ListBox with embedded CheckBox restricting one item to be checked - wpf

I have a ListBox containing an ItemTemplate made up of a StackPanel containing a CheckBox and a Label. I want to allow only one list item to be checked at a time. I am having trouble understanding how I can get this accomplished. Here is the XAML describing the listbox:
<ListBox Grid.Row="0"
Grid.Column="0"
Width="180"
HorizontalAlignment="Left"
x:Name="listboxPlayers"
ItemsSource="{Binding Players}"
SelectedItem="{Binding SelectedPlayer, Mode=TwoWay}"
ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsDefault, Mode=TwoWay}"
VerticalAlignment="Center"
Checked="CheckBox_Checked"
Unchecked="CheckBox_Unchecked"/>
<Label Content="{Binding Name}"
VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A Player is defined as this:
public class Player
{
public string Name { get; set; }
public bool IsDefault { get; set; }
}
My list of Players is defined like this:
public ObservableCollection<Player> Players { get; private set; }
My SelectedPlayer is defined like this:
public static readonly DependencyProperty SelectedPlayerProperty =
DependencyProperty.Register("SelectedPlayer", typeof(Player), typeof(MainWindow),
new FrameworkPropertyMetadata());
public Player SelectedPlayer
{
get { return (Player)GetValue(SelectedPlayerProperty); }
set { SetValue(SelectedPlayerProperty, value); }
}
I haven't been able to find a post or question that can help me. I've played with using a Checked event handler for the CheckBox but I can't seem to wrap my head around how I can relate the list to the correct Player in the list because the ListBox doesn't adjust the SelectedPlayer when the CheckBox is checked or unchecked.
Thanks very much.

I usually use the following style for displaying a ListBox using CheckBoxes (You can use RadioButtons too by just replacing the CheckBox control with a RadioButton
<Style x:Key="CheckBoxListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Cycle" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2, 2, 2, 0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent">
<CheckBox IsHitTestVisible="False" Focusable="false"
Content="{TemplateBinding ContentPresenter.Content}"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
Then it is simply used by something like
<ListBox ItemsSource="{Binding Players}"
Style="{StaticResource CheckBoxListBoxStyle}"
SelectedItem="{Binding SelectedPlayer, Mode=TwoWay}">

If you have something that should behave like a RadioButton, why not use a RadioButton?

Try configuring your ListBox to Selection Mode Single. And rather than a separate label you could just bind Name to the Content property of the CheckBox. I like the way that spaces. And you may need to implement InotifyPropertyChanged on player.

Related

WPF MVVM-Light adding an image to a listview item when selecting

I have a ListView that I want to be able to add a 'Delete' button to, with a command attached to it, when I select an item. I can't find anything about this on stack which is quite surprising.
One way you could do this is by applying a style to the ListViewItem in the ListView.Resources. In the Style, you set the ContentTemplate to a DataTemplate with only a TextBlock. Then you give the Style a Trigger that is tied to the IsSelected property. In the Trigger you'll set the ContentTemplate to a new DataTemplate with a TextBlock and a Button. The Command property of the Button is bound to the ViewModel using the RelativeSource binding and the CommandParameter is bound to the item so that it gets passed along as a parameter to your DeleteCommand.
XAML
<ListView Margin="3"
MinWidth="200"
ItemsSource="{Binding Items}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding SomeProperty}"
Margin="3" />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SomeProperty}"
Margin="3" />
<Button Content="Delete"
Margin="3"
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"
CommandParameter="{Binding }" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Resources>
</ListView>
ViewModel
private ICommand _DeleteCommand;
public ICommand DeleteCommand
{
get
{
if (_DeleteCommand == null)
{
_DeleteCommand = new RelayCommand<ExampleModel>(param => DeleteItem(param));
}
return _DeleteCommand;
}
}
private void DeleteItem(ExampleModel item)
{
MessageBox.Show(item.SomeProperty);
}
I just have the Delete method showing the value in a MessageBox for demo purposes. You should be able to modify the DataTemplate and Delete method to meet your needs.

Change WPF UserControl depending on a Property of a TreeViewItem

My application shows a TreeView on the left with hierarchical ordered items which are all the same type. All the items have a dependency property which can have one of two values. This value is an enum. Depending on this value I want to show one of two UserControls on the left. My idea was to insert both controls and set their opacity to 0. Then I wanted to insert a Style with a DataTrigger that triggers the opacity depending on the enum's value. But I can not access the properties of one control from the other control's DataTrigger; and the trigger does not seem to recognize the enum's value.
The enum:
public enum IdentityType
{
Person,
OrganisationUnit
}
The XAML:
<TreeView Grid.Column="0" Grid.Row="1" Background="AntiqueWhite" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Identities}" x:Name="OiTree">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Childs}">
<TextBlock Text="{Binding}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Controls:UcPerson Grid.Column="1" Grid.Row="1" Opacity="0">
<Controls:UcPerson.Style>
<Style TargetType="Controls:UcPerson">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Type, ElementName=OiTree.SelectedItem}" Value="Person">
<Setter Property="Opacity" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Controls:UcPerson.Style>
</Controls:UcPerson>
<Controls:UcOrgUnit Grid.Column="1" Grid.Row="1" Opacity="0">
<Controls:UcOrgUnit.Style>
<Style TargetType="Controls:UcOrgUnit">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Type, ElementName=OiTree.SelectedItem}" Value="OrganisationUnit">
<Setter Property="Opacity" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Controls:UcOrgUnit.Style>
</Controls:UcOrgUnit>
The problem is that you are setting the Opacity directly on the control first.
An explicit setting on a control will always override a trigger value.
However, a trigger value will override a style setter.
The following code should work (though I haven't tested it myself)
<Controls:UcPerson Grid.Column="1" Grid.Row="1">
<Controls:UcPerson.Style>
<Style TargetType="Controls:UcPerson">
<Style.Setters>
<Setter Property="Opacity" Value="0" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Type, ElementName=OiTree.SelectedItem}" Value="Person">
<Setter Property="Opacity" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Controls:UcPerson.Style>
See this question here for another example of the problem: DataTrigger not firing
As an aside, I believe your problem could be solved more elegantly using a DataTemplateSelector.
As Andrew implied the solution to my problem was a DataTemplateSelector. Instead of two userControls I created two templates and Used a ContentControl. The content Property of the ContentControl is bound to the SelectedItem of the TreeView and I implemented a simple DataTemplateSelector which casts the content to the original object an decides wich template to use. The source (modified) is from here: link
This is the xaml:
<Window.Resources>
<DataTemplate x:Key="borderTemplate">
<Border BorderThickness="1" BorderBrush="Brown" CornerRadius="5">
<TextBlock Margin="5" Text="Border Template"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="twoTextBlockTemplate">
<StackPanel>
<TextBlock Margin="5" Text="First TextBlock"/>
<TextBlock Margin="5" Text="Second TextBlock"/>
</StackPanel>
</DataTemplate>
<vm:OiContentTemplateSelector
x:Key="myContentTemplateSelector"
BorderTemplate="{StaticResource borderTemplate}"
TwoTextBlockTemplate="{StaticResource twoTextBlockTemplate}"/>
</Window.Resources>
This is the DataTemplateSelector:
public class OiContentTemplateSelector : DataTemplateSelector
{
public DataTemplate BorderTemplate
{ get; set; }
public DataTemplate TwoTextBlockTemplate
{ get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
OrganisationIdentity value = item as OrganisationIdentity;
if (value != null)
{
if (value.Type == IdentityType.Person)
return BorderTemplate;
else if (value.Type == IdentityType.OrganisationUnit)
return TwoTextBlockTemplate;
return base.SelectTemplate(item, container);
}
else
return base.SelectTemplate(item, container);
}
}
Perhaps this helps somebody

WPF ListBox selection issues, providing feedback to user

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

Data Binding with Silverlight accordion Control

I have Silverlight Accordion control in the ChildWindow and I customized it the following way
<Style x:Key=itemStyle TargetType=AccordionItem>
<Setter Porperty=HeaderTemplate>
<DataTemplate>
<TextBlock x:Name=_headertext/>
</DataTemplate>
</Setter>
</Style>
<Accordion Style"{StaticResource itemStyle}">
<Accordion.ContentTemplate>
<DataTemplate>
<StackPanel>
<CheckBox/>
<TextBlock x:name=_contenttext/>
</DataTemplate>
<Accordion.ContentTemplate>
</Accordion>
Now I have a method in my Chilwindow.Xaml
public void LoadItems(ObservableColection<Groups> gp)
{}
This method is called from the mainpage and it passes the gp value
Groups is a class with public properties and Observable collections.For example
public class Groups
{
public string FirstName{get, set;}
public ObservableCollection<Details> details {get, set;}
public Groups()
{
this.details=new ObservableCollection<Details>();
}
}
My Details Class is as follows
public class Details
{
public int id {get; set;}
public string LastName{get; set;}
--------
-------
}
Now I have to bind the _headertext(TextBlock in header Template) with the FirstName and _contenttext(TextBlock in Content Template) with LastName.
Please help me in doing this.I need your help.
Thanks
Rani
Why not use databinding in XAML directly? You should not need to do this in code.
<Style x:Key=itemStyle TargetType=AccordionItem>
<Setter Porperty=HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
</Setter>
</Style>
<Accordion Style"{StaticResource itemStyle}">
<Accordion.ContentTemplate>
<DataTemplate>
<StackPanel>
<CheckBox/>
<TextBlock Text="{Binding LastName}"/>
</DataTemplate>
<Accordion.ContentTemplate>
</Accordion>
First, the TargetType is pointed at AccordionItem and you are trying to use the style on the Accordion element itself. This will never work. In order to get this to work, you will need to create two styles, one for the Accordion itself and one for the AccordionItem that you reference within the style for the accordion.
<Style x:Key="itemStyle" TargetType="layoutToolkit:AccordionItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="accordionStyle" TargetType="layoutToolkit:Accordion">
<Setter Property="ItemContainerStyle" Value="{StaticResource itemStyle}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Then you define your accordion control like such:
<layoutToolkit:Accordion Height="Auto"
Name="accordion1"
ExpandDirection="Right"
SelectionMode="One"
ItemsSource="{Binding}"
Style="{StaticResource accordionStyle}">
</layoutToolkit:Accordion>

Data bound radio button list in WPF

I have a list of options in a data object, and I want to make the equivalent of a radio button list to allow the user to select one and only one of them. Functionality similar to a databound combo box, but in radio button format.
Silly me, I thought this would be built in, but no. How do you do it?
Basically, after reviewing the google results, I started with the info from an MSDN discussion thread where Dr. WPF provided an answer, which talks about styling a ListBox to look right. However, when the listbox is disabled, the background was an annoying color that I couldn't get rid of for the life of me, until I read the MSDN example of the ListBox ControlTemplate, which shows the secret Border element that was kicking my background butt.
So, the final answer here was this style:
<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
<!-- ControlTemplate taken from MSDN http://msdn.microsoft.com/en-us/library/ms754242.aspx -->
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="MinWidth" Value="120"/>
<Setter Property="MinHeight" Value="95"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border Name="Border" Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
CornerRadius="2">
<ScrollViewer Margin="0" Focusable="false">
<StackPanel Margin="2" IsItemsHost="True" />
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Border" Property="Background"
Value="Transparent" />
<Setter TargetName="Border" Property="BorderBrush"
Value="Transparent" />
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<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="{TemplateBinding IsSelected}">
<ContentPresenter />
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
Which provides a ControlTemplate for, and styles, the ListBox and the Items. And it gets used like this:
<ListBox Grid.Column="1" Grid.Row="0" x:Name="TurnChargeBasedOnSelector" Background="Transparent"
IsEnabled="{Binding Path=IsEditing}"
Style="{StaticResource RadioButtonList}"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainForm}}, Path=DataContext.RampTurnsBasedOnList}"
DisplayMemberPath="Description" SelectedValuePath="RampTurnsBasedOnID"
SelectedValue="{Binding Path=RampTurnsBasedOnID, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"/>
The more I spend time with WPF, the more I think it makes the trivial insanely difficult and the insanely difficult trivial. Enjoy. -Scott
Super Simple, MVVM friendly, leveraging DataTemplates for types.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Option}">
<RadioButton Focusable="False"
IsHitTestVisible="False"
Content="{Binding Display}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}">
</RadioButton>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Options}" SelectedItem="{Binding SelectedOption}"/>
</Grid>
View Model, etc:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Vm();
}
}
public class Vm
{
public Option[] Options { get { return new Option[] {
new Option() { Display = "A" },
new Option() { Display = "B" },
new Option() { Display = "C" } }; } }
public Option SelectedOption { get; set; }
}
public class Option
{
public string Display { get; set; }
}
If you wrap your option into a specific type (or likely it is already). You can just set a DataTemplate for that type, WPF will automatically use it. (Define DataTemplate in ListBox resources to limit the scope of where the DataTemplate will be applied).
Also use group name in the DataTemplate to set the group if you want.
This is much simpler than changing the control template, however it does mean that you get a blue line on selected items. (Again, nothing a bit of styling can't fix).
WPF is simple when you know how.
Bind the listbox to the ItemsSource of a ListBox with a list of objects that have a property Name (this can change)
<ListBox Name="RadioButtonList">
<ListBox.ItemTemplate >
<DataTemplate >
<RadioButton GroupName="radioList" Tag="{Binding}" Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
important GroupName="radioList"
I've done this through a ValueConverter that converts an enum to a bool. By passing the enum value that your radio button represents as the ConverterParameter, the converter returns whether this radio button should be checked or not.
<Window.Resources>
<Converters:EnumConverter x:Key="EnumConverter" />
</Window.Resources>
<RadioButton IsChecked="{Binding Path=MyEnum, Mode=TwoWay,
Converter={StaticResource EnumConverter},
ConverterParameter=Enum1}"}
Content="Enum 1" />
<RadioButton IsChecked="{Binding Path=MyEnum, Mode=TwoWay,
Converter={StaticResource EnumConverter},
ConverterParameter=Enum2}"}
Content="Enum 2" />
EnumConverter is defined as follows:
public class EnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType.IsAssignableFrom(typeof(Boolean)) && targetType.IsAssignableFrom(typeof(String)))
throw new ArgumentException("EnumConverter can only convert to boolean or string.");
if (targetType == typeof(String))
return value.ToString();
return String.Compare(value.ToString(), (String)parameter, StringComparison.InvariantCultureIgnoreCase) == 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType.IsAssignableFrom(typeof(Boolean)) && targetType.IsAssignableFrom(typeof(String)))
throw new ArgumentException("EnumConverter can only convert back value from a string or a boolean.");
if (!targetType.IsEnum)
throw new ArgumentException("EnumConverter can only convert value to an Enum Type.");
if (value.GetType() == typeof(String))
{
return Enum.Parse(targetType, (String)value, true);
}
//We have a boolean, as for binding to a checkbox. we use parameter
if ((Boolean)value)
return Enum.Parse(targetType, (String)parameter, true);
return null;
}
}
Note that I don't databind to the list of enums to generate the radio buttons, I've done them by hand. If you wanted to fill the list of radio buttons through a binding, I think you'll need to change the IsChecked binding to a MultiBinding which binds to both the current value and the radio's enum value, because you cannot use a binding on ConverterParameter.
Sorry, I'd like to put this response to Scott O's post as a comment on his post, but I do not yet have the reputation to do that. I really liked his answer as it was a style-only solution and hence didn't require any added code-behind or creating a custom-control, etc.
However, I did have one issue when I then went to try using controls inside the ListBoxItems. When I use this style I am unable to focus any of the contained controls due to this line:
<RadioButton Focusable="False"
IsHitTestVisible="False"
IsChecked="{TemplateBinding IsSelected}">
The radio button needs to turn off Focusable and IsHitTestVisible for the IsChecked binding to work correctly. To get around this, I changed the IsChecked from a TemplateBinding to a regular binding, which allowed me to make it a two-way binding. Removing the offending settings gave me this line:
<RadioButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected, Mode=TwoWay}">
Which now allows me to focus any controls contained in ListBoxItems as expected.
Hope this helps.
I took my inspiration from Jon Benson's blog entry, but modified his solution to use enumerations that have a description attribute. So the key parts of the solution became:
Enumerator with descriptions
public enum AgeRange {
[Description("0 - 18 years")]
Youth,
[Description("18 - 65 years")]
Adult,
[Description("65+ years")]
Senior,
}
Code for reading descriptions and returning key/value pairs for binding.
public static class EnumHelper
{
public static string ToDescriptionString(this Enum val)
{
var attribute =
(DescriptionAttribute)
val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false).
SingleOrDefault();
return attribute == default(DescriptionAttribute) ? val.ToString() : attribute.Description;
}
public static List<KeyValuePair<string,string>> GetEnumValueDescriptionPairs(Type enumType)
{
return Enum.GetValues(enumType)
.Cast<Enum>()
.Select(e => new KeyValuePair<string, string>(e.ToString(), e.ToDescriptionString()))
.ToList();
}
}
Your Object Data Provider in XAML
<ObjectDataProvider
ObjectType="{x:Type local:EnumHelper}"
MethodName="GetEnumValueDescriptionPairs"
x:Key="AgeRanges">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:AgeRange" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
Your ListBox in XAML
<ListBox
ItemsSource="{Binding Source={StaticResource AgeRanges}}"
SelectedValue="{Binding SelectedAgeRange}"
SelectedValuePath="Key">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Content="{Binding Value}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The property (e.g. in your view model) that you are binding to
public class YourViewModel : INotifyPropertyChanged
{
private AgeRange _selectedAgeRange;
public AgeRange SelectedAgeRange
{
get { return _selectedAgeRange; }
set
{
if (value != _selectedAgeRange)
{
_selectedAgeRange = value;
OnPropertyChanged("SelectedAgeRange");
}
}
}
}
I cheated:
My solution was to bind the list box programaticly since that is all that seemed to work for me:
if (mUdData.Telephony.PhoneLst != null)
{
lbPhone.ItemsSource = mUdData.Telephony.PhoneLst;
lbPhone.SelectedValuePath = "ID";
lbPhone.SelectedValue = mUdData.Telephony.PrimaryFaxID;
}
The XAML looks like this:
<ListBox.ItemTemplate >
<DataTemplate >
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<RadioButton
IsChecked="{Binding Path=PrimaryPhoneID}"
GroupName="Phone"
x:Name="rbPhone"
Content="{Binding Path=PrimaryPhoneID}"
Checked="rbPhone_Checked"/>
<CheckBox Grid.Column="2" IsEnabled="False" IsChecked="{Binding Path=Active}" Content="{Binding Path=Number}" ></CheckBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
And in my event to read the value of the radio button as it is selected looks like this:
private void rbPhone_Checked(object sender, RoutedEventArgs e)
{
DataRowView dvFromControl = null;
dvFromControl = (DataRowView)((RadioButton)sender).DataContext;
BindData.Telephony.PrimaryPhoneID = (int)dvFromControl["ID"];
}
Hope that helps someone.

Resources