WPF Databound RadioButton ListBox - wpf

I am having problems getting a databound radiobutton listbox in WPF to respond to user input and reflect changes to the data it's bound to (i.e., to making changes in the code). The user input side works fine (i.e., I can select a radiobutton and the list behaves as expected). But every attempt to change the selection in code fails. Silently (i.e., no exception).
Here's the relevant section of the XAML (I think):
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="theBorder" Background="Transparent">
<RadioButton Focusable="False" IsHitTestVisible="False"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" >
<ContentPresenter />
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
I bind the listbox to a List of SchoolInfo objects. SchoolInfo contains a property called IsSelected:
public bool IsSelected
{
get { return isSelected; }
set
{
if( value != isSelected )
{
isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
The OnPropertyChanged() stuff was something I put in during my experimentation. It doesn't solve the problem.
Things like the following fail:
((SchoolInfo) lbxSchool.Items[1]).IsSelected = true;
lbxSchool.SelectedIndex = 1;
They fail silently -- no exception is thrown, but the UI doesn't show the item being selected.

The RadioButton is binding to the ListBoxItem IsSelected property, not to your SchoolInfo IsSelected property.
(It is confusing because ListBoxItem has an "IsSelected" property, and so also does your SchoolInfo object, which is why there were no binding errors).
To fix, ListBoxItem.IsSelected needs to be bound to your SchoolInfo IsSelected property.
i.e. You need an extra setter for the ListBoxItem to bind to SchoolInfo.IsSelected, and then the list box item will work as it should, and also the RadioButton can bind correctly to ListBoxItem.IsSelected.
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />

I would enable NotifyOnSourceUpdated in your RadioButton binding. Even though you are allowing a two-way binding (which is the default), notifications won't be picked up from code-behind changes unless you are explicitly listening for them.
<RadioButton Focusable="False" IsHitTestVisible="False"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, NotifyOnSourceUpdated=True}" >

Related

Style of a TextBox: Why is 'AcceptsReturn=true' not applied?

I have a custom control in WPF, which consists of a toggle button, a TextBlock and a TextBox. What I basically want to do is to show the TextBox when the toggle button is checked and the TextBlock otherwise. Furthermore I want allow defining to style properties on the control via dependency properties, which are applied to the TextBlock and the TextBox at runtime. The default template looks like this:
<Style TargetType="{x:Type views:EditableLabel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type views:EditableLabel}">
<Border Background="{TemplateBinding Background}">
<DockPanel Margin="0">
<telerik:RadToggleButton x:Name="PART_Toggle"
DockPanel.Dock="Right"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsInEditMode, Mode=TwoWay}">
<Image Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ToggleImage}" Height="14" />
</telerik:RadToggleButton>
<TextBlock x:Name="PART_TextBlock"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text}" >
</TextBlock>
<TextBox x:Name="PART_TextBox"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
</TextBox>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The custom control has two dependency properties for styles, one for the PART_TextBlock and one for PART_TextBox. The styles are assigned in the OnApplyTemplate method of the custom control and in the property change callbacks of the two dependency properties:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_textBlock = (TextBlock) GetTemplateChild("PART_TextBlock");
_textBox = (TextBox) GetTemplateChild("PART_TextBox");
_toggleButton = (RadToggleButton) GetTemplateChild("PART_Toggle");
ApplyStyles();
UpdateVisibilities();
}
private void ApplyStyles()
{
if (_textBlock != null) _textBlock.Style = TextBlockStyle;
if (_textBox != null) _textBox.Style = TextBoxStyle;
}
(The callbacks are not shown here, as they are trivial, just calling ApplyStyles().
I use the custom control like this:
<views:EditableLabel Text="{Binding SelectedToolbox.Description, Mode=TwoWay}"
CanEdit="{Binding SelectedToolbox.CanEdit}"
ToggleImage="../Resources/Images/edit-26.png">
<views:EditableLabel.TextBlockStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</views:EditableLabel.TextBlockStyle>
<views:EditableLabel.TextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="AcceptsReturn" Value="True" />
<Setter Property="VerticalScrollBarVisibility" Value="Visible" />
</Style>
</views:EditableLabel.TextBoxStyle>
</views:EditableLabel>
Everything works as expected, except from the AcceptsReturn setter is not applied, which I find very strange. I've debugged ApplyStyles(): The style is assigned correctly and both setters are contained within the style.
TextWrapping and VerticalScrollBarVisibility are both set correctly:
while AcceptsReturn is not:
Any ideas, what might be the issue here?
The screenshot you posted suggests AcceptsReturn has a local value, i.e., a value set by explicitly calling the property setter or SetValue. Do you have any code in EditableLabel which explicitly sets the AcceptsReturn property? If so, the local value you set will take precedence over any style setters. You can avoid this by using SetCurrentValue to change the value while leaving the value source unchanged.
Secondly, rather assigning the style in your code behind, it is generally easier and more reliable to simply bind the style within the template, e.g.:
<TextBox x:Name="PART_TextBox" Style="{TemplateBinding TextBoxStyle}" ... />
You might try this first and see if you get better results.

Listbox doesn't detect selected item

I have the following listbox:
<ListBox ItemsSource="{Binding AvailableTemplates}" Style="{DynamicResource SearchListBoxStyle}" SelectedItem="{Binding SelectedTemplate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding}" GroupName="group" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
this doesn't detect as selected item changed if i select on the radiobutton. It only detects if i click under the radio button on the listbox row. Any ideeas how to amend to detect as selected item changed when clicking on the radio button?
If you only want to synchronize the RadioButton.IsChecked with ListBoxItem.IsSelected, you can use a binding
<RadioButton Content="{Binding}" GroupName="group"
IsChecked="{Binding Path=IsSelected, RelativeSource={
RelativeSource AncestorType={x:Type ListBoxItem}},Mode=TwoWay}"/>
If you don't want your items synchronized, you can use a Trigger that sets IsSelected whenever the item gets keyboard focus, although this will only keep an item selected as long as it has keyboard focus
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
And if you want it selected regardless of if the element still has keyboard focus or not, you have to use a little code behind
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewGotKeyboardFocus" Handler="SelectCurrentItem"/>
</Style>
protected void SelectCurrentItem(object sender, KeyboardFocusChangedEventArgs e)
{
ListBoxItem item = (ListBoxItem)sender;
item.IsSelected = true;
}
You can not mistake: listBox.SelectedItem and radioButton.IsChecked
They are totally different things,
SelectedItem is called the ListBoxItem, your radiobutton is within a listboxitem.
You must make a binding for property IsChecked (RadioButton).
Try setting ListBoxItem.IsHitTestVisible to false (you may do this in xaml). It solved my selection problem elementary. My problem was that selection only worked when I clicked on white space in ListBox line but not a custom content.

Binding a Command inside a Style

I'm currently working on a WPF application that uses MVVM. I've got a ListBox with a style set up to make it display like a RadioButtonList as follows:
<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
<Setter Property="BorderBrush" Value="{x:Null}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Background="Transparent">
<RadioButton Focusable="False" IsHitTestVisible="False" IsChecked="{TemplateBinding IsSelected}" Content="{Binding Path=DisplayName}" Command="{Binding ElementName=ShippingWindow, Path=DataContext.ShipOtherMethodSelected}">
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
<ListBox Name="lbShipOtherMethodOptions" Style="{StaticResource RadioButtonList}" ItemsSource="{Binding Path=ShipOtherMethodOptions}" Margin="13,74,366,282" />
What I'm trying to do is bind a command to the RadioButton, so that I can fire off an event when a selection is made. I've got the following code in my viewmodel, but I can't seem to get it to fire:
private ICommand shipOtherMethodSelected;
public ICommand ShipOtherMethodSelected
{
get
{
return shipOtherMethodSelected ??
(shipOtherMethodSelected = new RelayCommand(param => ShipOpenItems(), param => true));
}
}
private void ShipOpenItems()
{
MessageBox.Show("GOT HERE");
}
I'm pretty new to WPF and MVVM, so I'm probably missing something obvious. Can anyone point me in the right direction?
EDIT:
Per jberger's suggestion, I put in some code that I attempted that didn't work. Setting breakpoints in that section didn't get tripped, nor did the message box show up.
EDIT 2:
So after inspecting the DataContext on the sender, it turns out it was pointing to the object that I'm binding the RadioButton to, and not my viewmodel. I updated the code above (adding in an x:Name to my window and updating the Command binding), and now I'm getting the event to fire when its initially bound, but it doesn't fire when I select a value. Seems like we're getting really close now.
The ShipOtherMethodSelected is in your (main) ShippingVM NOT your ShipItemVM, so you need to set
Command="{Binding ElementName=ShippingWindow, Path=DataContext.ShipOtherMethodSelected}"
where ShippingWindow is the x:Name of an element "above" the ListBoxItem
Also, the Focusable="False" IsHitTestVisible="False" is denying the click. Remove the setters.

Binding to a cutom property of the template parent

Q: How do I bind to a custom property of the template parent from a child control's style DataTrigger
I've been scratching my head over this one for a couple of days.
I have a databound TreeView which uses a Style which has a Template. The TreeView is bound to a ObservableCollection and a HierarchicalDataTemplate + DataTemplate bind to properties inside a collection item.
FontGroup -> Font(s)
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Image x:Name="ExpanderImage" Source="/Typesee;component/Resources/tree_expand.png" RenderOptions.BitmapScalingMode="NearestNeighbor" />
<ControlTemplate.Triggers>
<DataTrigger Binding="??? IsItemSelected ???" Value="True">
<Setter TargetName="ExpanderImage" Property="Source" Value="/Typesee;component/Resources/tree_collapse_selected.png" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="FontTreeViewTemplate" TargetType="{x:Type TreeViewItem}">
...
<ToggleButton x:Name="Expander" Style="{StaticResource ExpandCollapseToggleStyle}" ... />
...
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsItemSelected}" Value="True">
<!-- WORKS FINE HERE -->
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
First I tried to bind like:
Binding Path=IsItemSelected, RelativeSource={RelativeSource TemplatedParent}
Then I read that might not work so I tried (including AncestorLevel 1+3):
Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}, AncestorLevel=2
Have also tried combos with UpdateSourceTrigger=PropertyChanged and Mode=TwoWay
If this is a flawed design please suggest a way of doing this: I basically want to change the image of the expand toggle button based on whether the property IsItemSelected is true on the TreeViewItem -- any ideas?
Thanks so much for any help!
The viewmodel in all likelihood will be the DataContext, so the binding should be a RelativeSource binding with a respective path which needs to explicity target the DataContext as the new source is the RelativeSource:
{Binding DataContext.IsItemSelected,
RelativeSource={RelativeSource AncestorType=TreeViewItem}}
As noted in my comment it might be advisable to extract this logic from the ControlTemplate as this leaves its bounds. One method would be subclassing the ToggleButton and exposing a public property for the image which then can be changed via a Style.

ListView ItemContainerStyle Template

I created style to be used as my ListView's item template that contains a CheckBox and a TextBlock:
<Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListView}">
<Setter Property="SelectionMode" Value="Multiple"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListViewItem}" >
<Setter Property="Template">
...
The checkbox in the template is bound to the IsSelected property of the list view item:
<CheckBox x:Name="itemCheckBox" Width="13" Height="13" Focusable="False" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
And the textblock's text is bound to the Value property of the source of the list view item:
<TextBlock x:Name="textBlock" Padding="5,0,0,0" HorizontalAlignment="Stretch">
<ContentPresenter Content="{Binding Path=Value}"/>
</TextBlock>
Each of the items in my list is an object that contains two properties, Value and IsChecked. What I am trying to do is bind the IsChecked property of my object to the IsSelected property of my list view item. However, I do not know how to access the IsChecked property of my object from within the ListViewItem template.
I didn't have any trouble binding the content of the text block to the Value property of my object but where would I even put the binding definition if I want the IsChecked property of my object to be bound to the IsSelected property of the list view item?
The DataContext for each ListViewItem should be set to the item data when it is created by the parent ListView so you should be able to use:
<Style TargetType="{x:Type ListViewItem}" >
<Setter Property="IsSelected" Value="{Binding IsChecked}">
...
</Style>

Resources