Converter never called when binding to Relative source - wpf

My Binding :
<StackPanel x:Name="Ancestor">
<StackPanel.Resources>
<converters:DiceInputToVisualConverter x:Key="MyDiceInputToVisualConverter" />
<Style TargetType="{x:Type Ellipse}">
<Setter Property="Visibility" Value="{Binding Path=/, Converter={StaticResource MyDiceInputToVisualConverter},FallbackValue=Visible}"></Setter>
</Style>
<StackPanel.Resources>
<StackPanel>
<Canvas DataContext="{Binding Path=DataContext.Dice1,RelativeSource={RelativeSource AncestorType=StackPanel}}">
<Ellipse Canvas.Left="5" Canvas.Top="5"></Ellipse>
<Ellipse Canvas.Left="5" Canvas.Top="20"></Ellipse>
</Canvas>
</StackPanel>
the DataContext :
Ancestor.DataContext = game ;
the converter :
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int dice = int.Parse(value.ToString());
if (dice == 3)
return Visibility.Visible;
return Visibility.Hidden;
}
my data source :
public Class Game : INotifyPropertyChanged
{
private int dice1;
public int Dice1
{
get { return dice1; }
set
{
dice1 = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Dice1"));
}
}
}
the binding is fine when i checked it with snoop the ellipse's DataContext had the desired value
but still the Converter is never called any ideas ?

Are you sure that you should be using Path=/? This notation means the currently selected item of the default collection view.

I see you have called a static resource
StaticResource MyDiceInputToVisualConverter
Where have you declared this resource. for example in the Window.Resources section Like the following.
<Window.Resources>
<!-- Converters that are used on the MainWindow. -->
<Converters:MyDiceInputToVisualConverter x:Key="MyDiceInputToVisualConverter" />
</Window.Resources>

Related

Set ContentPresenter ContentTemplate on value change in generated field

I have am attempting to build a tree view where:
1. The TreeViewItems are generated by a list in my model.
2. Each TreeViewItem contains a ComboBox, and a dynamic element whose template I want to change based on the value selected in the ComboBox.
Here is my current xaml code.
<Window x:Class="MyTestWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyTestWPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:NodeTypeToTemplateConverter x:Key="NodeTypeToTemplateConverter"/>
<DataTemplate x:Key="Template1">
<TextBlock Text="Template 1" />
</DataTemplate>
<DataTemplate x:Key="Template2">
<TextBlock Text="Template 2" />
</DataTemplate>
<Style x:Key="MyNodeTemplate" TargetType="ContentPresenter">
<Setter Property="ContentTemplate" Value="{StaticResource Template1}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NodeType}">
<DataTrigger.Value>
<local:NodeTypesEnum>Type1</local:NodeTypesEnum>
</DataTrigger.Value>
<Setter Property="ContentTemplate" Value="{Binding Converter={StaticResource NodeTypeToTemplateConverter}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:MyTreeNode}"
ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=GetAvailableNodeType}"
SelectedItem="{Binding Path=NodeType}" />
<ContentPresenter Style="{StaticResource MyNodeTemplate}" Content="{Binding}" />
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView x:Name="MyTree" ItemsSource="{Binding MyTreeModel}" />
</Window>
And its code-behind:
using System.Windows;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new
{
MyTreeModel = new MyTreeNode[] {
new MyTreeNode() { Name = "1", Nodes = new MyTreeNode[] { new MyTreeNode() { Name= "2" } } }
}
};
}
}
The tree node type:
namespace MyTestWPF
{
public class MyTreeNode
{
public string Name { get; set; }
public NodeTypesEnum NodeType { get; set; }
public MyTreeNode[] Nodes { get; set; }
public NodeTypesEnum[] GetAvailableNodeType()
{
return new NodeTypesEnum[] { NodeTypesEnum.Type1, NodeTypesEnum.Type2 };
}
}
public enum NodeTypesEnum
{
Type1 = 0,
Type2 = 1
}
}
The Converter (NodeTypeToTemplateConverter) receives the whole ViewModel, and returns the name of the relevant template based on values in the model.
using System;
using System.Globalization;
using System.Windows.Data;
namespace MyTestWPF
{
public class NodeTypeToTemplateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if((value as MyTreeNode).NodeType == NodeTypesEnum.Type1)
{
return "Template1";
} else
{
return "Template2";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The problem is that the above code causes a stack overflow exception. The first item in the TreeView endlessly calls NodeTypeToTemplateConverter's Convert method.
I figured it had to do with the DataTrigger.Value. Setting that to a value different from the default NodeType allows the page to load without overflow, but the moment any ComboBox is set to NodeType1, stack overflow.
I attempted to simply remove the DataTrigger.Value element, but that causes the Converter to never be called at all...
How can I dynamically build the template name based on the value selected by its neighboring ComboBox?
You probably want to use a DataTemplateSelector rather than a converter.
public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
public DataTemplate Template1 { get; set; }
public DataTemplate Template2 { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//Logic to select template based on 'item' value.
if (item == <template1Value>) return Template1; //TODO: replace <template1Value>
else if (item == <template2Value>) return Template2; //TODO: replace <template2Value>
else return new DataTemplate();
}
}
<local:ComboBoxItemTemplateSelector x:Key="ComboBoxItemTemplateSelector">
<local:ComboBoxItemTemplateSelector.Template1>
<DataTemplate>
<TextBlock Text="" />
</DataTemplate>
</local:ComboBoxItemTemplateSelector.Template1>
<local:ComboBoxItemTemplateSelector.Template2>
<DataTemplate>
<TextBlock Text="" />
</DataTemplate>
</local:ComboBoxItemTemplateSelector.Template2>
</local:ComboBoxItemTemplateSelector>
<ContentPresenter Content="{Binding NodeType}" ContentTemplateSelector="{StaticResource ComboBoxItemTemplateSelector}"/>
I have not fully tested this code, so let me know if you have any issues.
EDIT:
The template selector is only executed when the content changes so this won't work if you use {Binding}. A workaround for this would be to have the DataTemplate content bind to the parent's DataContext.
<DataTemplate>
<TextBlock Text="" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContentPresenter}}"/>
</DataTemplate>
If this workaround is not acceptable, there are other ways to do this as well.

Enable/Disable controls on ComboBox selection in Xaml

How to enable /disable controls like textbox,label,textblock if combobox is selected/not-selected? e.g. If selected index is greater than zero, enable controls else disable.How to bind IsEnabled properties of the control with combobox selection?
You can bind IsEnabled to the SelectedIndex property of the ComboBox and use a IValueConverter to convert it to Boolean. For instance, in your XAML (showing enabling a Button):
<ComboBox x:Name="cmbBox" ItemsSource="{Binding Source={StaticResource DataList}}"/>
<Button Grid.Column="1" IsEnabled="{Binding ElementName=cmbBox, Path=SelectedIndex, Converter={StaticResource IndexToBoolConverter}}"/>
Then you need a converter as well, such as:
public class IndexToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)value > 0)
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You'll also have declare the Converter as a resource, such as in your Window.
<local:IndexToBoolConverter x:Key="IndexToBoolConverter"/>
I would probably just do something like this.
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=TheCombo}"
Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ComboBox x:Name="TheCombo" Width="100">
<ComboBoxItem>Blah</ComboBoxItem>
<ComboBoxItem>Blah</ComboBoxItem>
<ComboBoxItem>Blah</ComboBoxItem>
</ComboBox>
<Button Content="Click Me" Margin="0,10"/>
</StackPanel>
</Grid>
Hope this helps, cheers!
Try with this
Dispatcher.BeginInvoke(new Action(() =>
{
ToggleButton dropDownButton = GetFirstChildOfType<ToggleButton>(cboMedicos);
if (dropDownButton != null)
{
dropDownButton.IsEnabled = false;
}
}), System.Windows.Threading.DispatcherPriority.Render);
public static T GetFirstChildOfType<T>(DependencyObject dependencyObject) where T : DependencyObject
{
if (dependencyObject == null)
{
return null;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var result = (child as T) ?? GetFirstChildOfType<T>(child);
if (result != null)
{
return result;
}
}
return null;
}

How to search for a wpf list box item value and highlight the index based on the value?

In my application I would like you highlight an index based on the value. for example:
ArrayList itemsList = new ArrayList();
private void button_Click(object sender, RoutedEventArgs e)
{
itemsList.Add("Coffie");
itemsList.Add("Tea");
itemsList.Add("Orange Juice");
itemsList.Add("Milk");
itemsList.Add("Mango Shake");
itemsList.Add("Iced Tea");
itemsList.Add("Soda");
itemsList.Add("Water");
listBox.ItemsSource = itemsList;
ApplyDataBinding();
}
private void ApplyDataBinding()
{
listBox.ItemsSource = null;
listBox.ItemsSource = itemsList;
}
It does not matter where in the listbox "Orange Juice" is I would like to highlight it based on its value. If the Position change it should be still highlighted. (Not based on the selected index)
If you want to highlight an item based on it's value, then you need to define your own datatemplate for an item and use a converter to provide appropriate brush for the background. Something like that:
<Window.Resources>
<local:TextToBrushConverter x:Key="TextToBrushConverter" />
</Window.Resources>
<Grid>
<ListBox Name="listBox" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Background="{Binding ., Converter={StaticResource TextToBrushConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Converter
class TextToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value as String) == "Orange Juice")
{
return Brushes.Orange;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I would recommend that you place the items into a class, think Object Oriented design and then work off of properties (as flags) to give different states.
Then by using Xaml styles to key off those different properties to achieve the affect you are looking for.
Say for example we have an Order class with these properties
public class Order
{
public string CustomerName { get; set; }
public int OrderId { get; set; }
public bool InProgress { get; set; }
}
When an order is marked as in progress (InProgress = true) we want to show red in our list box say for "Alpha" and "Omega" which are in progress:
ListBox Xaml
Here is the Xaml which binds to our data (how you bind is up to you) and shows how to work with Style(s), DataTemplate, and DataTrigger(s) to achieve that:
<ListBox ItemsSource="{StaticResource Orders}"
x:Name="lbOrders">
<ListBox.Resources>
<DataTemplate DataType="{x:Type model:Order}">
<TextBlock Text="{Binding Path=CustomerName}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=InProgress}"
Value="True">
<Setter Property="Foreground"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
Here is the data setup in the pages's resource Xaml to do that, but it could be created in code behind:
<Window.Resources>
<model:Orders x:Key="Orders">
<model:Order CustomerName="Alpha"
OrderId="997"
InProgress="True" />
<model:Order CustomerName="Beta"
OrderId="998"
InProgress="False" />
<model:Order CustomerName="Omega"
OrderId="999"
InProgress="True" />
<model:Order CustomerName="Zeta"
OrderId="1000"
InProgress="False" />
</model:Orders>
</Window.Resources>
This should give you enough to start on and create a full featured UI.

Distinct item template for first and last item in a ListView

I need to style the first and last items of a list view differently. To achieve that, I started working on a solution based on that answer: Use different template for last item in a WPF itemscontrol
Basically, I have a custom ItemsTemplateSelector that decide on the template to apply based on the item's index in the list view items (code below).
It works properly except that when the list gets updated (an item is added or removed), the templates do not get selected again (for instance, initially, the SingleItemTemplate gets selected because there is a single item. When I add an item to the list, that first item's template does not get switched to FirstItemTemplate). How to force template selection for all items?
public class FirstLastTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate FirstItemTemplate { get; set; }
public DataTemplate LastItemTemplate { get; set; }
public DataTemplate SingleItemTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ListView lv = VisualTreeHelperEx.FindParentOfType<ListView>(container);
if (lv != null)
{
if (lv.Items.Count == 1)
{
return SingleItemTemplate;
}
int i = lv.Items.IndexOf(item);
if (i == 0)
{
return FirstItemTemplate;
}
else if (i == lv.Items.Count - 1)
{
return LastItemTemplate;
}
}
return DefaultTemplate;
}
}
As an alternative approach, I would suggest binding the AlternationCount of your ItemsControl to the number of items in your collection (e.g. the Count property). This will then assign to each container in your ItemsControl a unique AlternationIndex (0, 1, 2, ... Count-1). See here for more information:
http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.alternationcount.aspx
Once each container has a unique AlternationIndex you can use a DataTrigger in your container Style to set the ItemTemplate based off of the index. This could be done using a MultiBinding with a converter that returns True if the index is equal the count, False otherwise. Of course you could also build a selector around this approach as well. With the exception of the converter, this approach is nice since it is a XAML only solution.
An example using a ListBox:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:l="clr-namespace:WpfApplication4"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<Collections:ArrayList x:Key="MyCollection">
<System:String>Item One</System:String>
<System:String>Item Two</System:String>
<System:String>Item Three</System:String>
</Collections:ArrayList>
<l:MyAlternationEqualityConverter x:Key="MyAlternationEqualityConverter" />
<Style x:Key="MyListBoxItemStyle" TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource MyAlternationEqualityConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListBox}}" Path="Items.Count" />
<Binding RelativeSource="{RelativeSource Self}" Path="(ItemsControl.AlternationIndex)" />
</MultiBinding>
</DataTrigger.Binding>
<!-- Could set the ItemTemplate instead -->
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource MyCollection}}"
AlternationCount="{Binding RelativeSource={RelativeSource Self}, Path=Items.Count}"
ItemContainerStyle="{StaticResource MyListBoxItemStyle}" />
</Grid>
Where the converter might look something like:
class MyAlternationEqualityConverter : IMultiValueConverter
{
#region Implementation of IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null && values.Length == 2 &&
values[0] is int && values[1] is int)
{
return Equals((int) values[0], (int) values[1] + 1);
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}

Simple WPF RadioButton Binding?

What is the simplest way to bind a group of 3 radiobuttons to a property of type int for values 1, 2, or 3?
I came up with a simple solution.
I have a model.cs class with:
private int _isSuccess;
public int IsSuccess { get { return _isSuccess; } set { _isSuccess = value; } }
I have Window1.xaml.cs file with DataContext set to model.cs. The xaml contains the radiobuttons:
<RadioButton IsChecked="{Binding Path=IsSuccess, Converter={StaticResource radioBoolToIntConverter}, ConverterParameter=1}" Content="one" />
<RadioButton IsChecked="{Binding Path=IsSuccess, Converter={StaticResource radioBoolToIntConverter}, ConverterParameter=2}" Content="two" />
<RadioButton IsChecked="{Binding Path=IsSuccess, Converter={StaticResource radioBoolToIntConverter}, ConverterParameter=3}" Content="three" />
Here is my converter:
public class RadioBoolToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int integer = (int)value;
if (integer==int.Parse(parameter.ToString()))
return true;
else
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return parameter;
}
}
And of course, in Window1's resources:
<Window.Resources>
<local:RadioBoolToIntConverter x:Key="radioBoolToIntConverter" />
</Window.Resources>
I am very surprised nobody came up with this kind of solution to bind it against bool array. It might not be the cleanest, but it can be used very easily:
private bool[] _modeArray = new bool[] { true, false, false};
public bool[] ModeArray
{
get { return _modeArray ; }
}
public int SelectedMode
{
get { return Array.IndexOf(_modeArray, true); }
}
in XAML:
<RadioButton GroupName="Mode" IsChecked="{Binding Path=ModeArray[0], Mode=TwoWay}"/>
<RadioButton GroupName="Mode" IsChecked="{Binding Path=ModeArray[1], Mode=TwoWay}"/>
<RadioButton GroupName="Mode" IsChecked="{Binding Path=ModeArray[2], Mode=TwoWay}"/>
NOTE: you don't need two-way binding if you don't want to one checked by default. TwoWay binding is the biggest con of this solution.
Pros:
No need for code behind
No need for extra class (IValue Converter)
No need for extra enums
doesn't require bizarre binding
straightforward and easy to understand
doesn't violate MVVM (heh, at least I hope so)
Actually, using the converter like that breaks two-way binding, plus as I said above, you can't use that with enumerations either. The better way to do this is with a simple style against a ListBox, like this:
Note: Contrary to what DrWPF.com stated in their example, do not put the ContentPresenter inside the RadioButton or else if you add an item with content such as a button or something else, you will not be able to set focus or interact with it. This technique solves that. Also, you need to handle the graying of the text as well as removing of margins on labels or else it will not render correctly. This style handles both for you as well.
<Style x:Key="RadioButtonListItem" TargetType="{x:Type ListBoxItem}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<DockPanel LastChildFill="True" Background="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Center" >
<RadioButton IsChecked="{TemplateBinding IsSelected}" Focusable="False" IsHitTestVisible="False" VerticalAlignment="Center" Margin="0,0,4,0" />
<ContentPresenter
Content = "{TemplateBinding ContentControl.Content}"
ContentTemplate = "{TemplateBinding ContentControl.ContentTemplate}"
ContentStringFormat = "{TemplateBinding ContentControl.ContentStringFormat}"
HorizontalAlignment = "{TemplateBinding Control.HorizontalContentAlignment}"
VerticalAlignment = "{TemplateBinding Control.VerticalContentAlignment}"
SnapsToDevicePixels = "{TemplateBinding UIElement.SnapsToDevicePixels}" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RadioButtonList" TargetType="ListBox">
<Style.Resources>
<Style TargetType="Label">
<Setter Property="Padding" Value="0" />
</Style>
</Style.Resources>
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="ItemContainerStyle" Value="{StaticResource RadioButtonListItem}" />
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="HorizontalRadioButtonList" BasedOn="{StaticResource RadioButtonList}" TargetType="ListBox">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel Background="Transparent" Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
You now have the look and feel of radio buttons, but you can do two-way binding, and you can use an enumeration. Here's how...
<ListBox Style="{StaticResource RadioButtonList}"
SelectedValue="{Binding SomeVal}"
SelectedValuePath="Tag">
<ListBoxItem Tag="{x:Static l:MyEnum.SomeOption}" >Some option</ListBoxItem>
<ListBoxItem Tag="{x:Static l:MyEnum.SomeOtherOption}">Some other option</ListBoxItem>
<ListBoxItem Tag="{x:Static l:MyEnum.YetAnother}" >Yet another option</ListBoxItem>
</ListBox>
Also, since we explicitly separated out the style that tragets the ListBoxItem rather than putting it inline, again as the other examples have shown, you can now create a new style off of it to customize things on a per-item basis such as spacing. (This will not work if you simply try to target ListBoxItem as the keyed style overrides generic control targets.)
Here's an example of putting a margin of 6 above and below each item. (Note how you have to explicitly apply the style via the ItemContainerStyle property and not simply targeting ListBoxItem in the ListBox's resource section for the reason stated above.)
<Window.Resources>
<Style x:Key="SpacedRadioButtonListItem" TargetType="ListBoxItem" BasedOn="{StaticResource RadioButtonListItem}">
<Setter Property="Margin" Value="0,6" />
</Style>
</Window.Resources>
<ListBox Style="{StaticResource RadioButtonList}"
ItemContainerStyle="{StaticResource SpacedRadioButtonListItem}"
SelectedValue="{Binding SomeVal}"
SelectedValuePath="Tag">
<ListBoxItem Tag="{x:Static l:MyEnum.SomeOption}" >Some option</ListBoxItem>
<ListBoxItem Tag="{x:Static l:MyEnum.SomeOtherOption}">Some other option</ListBoxItem>
<ListBoxItem Tag="{x:Static l:MyEnum.YetAnother}" >Ter another option</ListBoxItem>
</ListBox>
I know it's way way overdue, but I have an alternative solution, which is lighter and simpler. Derive a class from System.Windows.Controls.RadioButton and declare two dependency properties RadioValue and RadioBinding. Then in the class code, override OnChecked and set the RadioBinding property value to that of the RadioValue property value. In the other direction, trap changes to the RadioBinding property using a callback, and if the new value is equal to the value of the RadioValue property, set its IsChecked property to true.
Here's the code:
public class MyRadioButton : RadioButton
{
public object RadioValue
{
get { return (object)GetValue(RadioValueProperty); }
set { SetValue(RadioValueProperty, value); }
}
// Using a DependencyProperty as the backing store for RadioValue.
This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadioValueProperty =
DependencyProperty.Register(
"RadioValue",
typeof(object),
typeof(MyRadioButton),
new UIPropertyMetadata(null));
public object RadioBinding
{
get { return (object)GetValue(RadioBindingProperty); }
set { SetValue(RadioBindingProperty, value); }
}
// Using a DependencyProperty as the backing store for RadioBinding.
This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadioBindingProperty =
DependencyProperty.Register(
"RadioBinding",
typeof(object),
typeof(MyRadioButton),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnRadioBindingChanged));
private static void OnRadioBindingChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
MyRadioButton rb = (MyRadioButton)d;
if (rb.RadioValue.Equals(e.NewValue))
rb.SetCurrentValue(RadioButton.IsCheckedProperty, true);
}
protected override void OnChecked(RoutedEventArgs e)
{
base.OnChecked(e);
SetCurrentValue(RadioBindingProperty, RadioValue);
}
}
XAML usage:
<my:MyRadioButton GroupName="grp1" Content="Value 1"
RadioValue="val1" RadioBinding="{Binding SelectedValue}"/>
<my:MyRadioButton GroupName="grp1" Content="Value 2"
RadioValue="val2" RadioBinding="{Binding SelectedValue}"/>
<my:MyRadioButton GroupName="grp1" Content="Value 3"
RadioValue="val3" RadioBinding="{Binding SelectedValue}"/>
<my:MyRadioButton GroupName="grp1" Content="Value 4"
RadioValue="val4" RadioBinding="{Binding SelectedValue}"/>
Hope someone finds this useful after all this time :)
I've come up with solution using Binding.DoNothing returned from converter which doesn't break two-way binding.
public class EnumToCheckedConverter : IValueConverter
{
public Type Type { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == Type)
{
try
{
var parameterFlag = Enum.Parse(Type, parameter as string);
if (Equals(parameterFlag, value))
{
return true;
}
}
catch (ArgumentNullException)
{
return false;
}
catch (ArgumentException)
{
throw new NotSupportedException();
}
return false;
}
else if (value == null)
{
return false;
}
throw new NotSupportedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is bool check)
{
if (check)
{
try
{
return Enum.Parse(Type, parameter as string);
}
catch(ArgumentNullException)
{
return Binding.DoNothing;
}
catch(ArgumentException)
{
return Binding.DoNothing;
}
}
return Binding.DoNothing;
}
throw new NotSupportedException();
}
}
Usage:
<converters:EnumToCheckedConverter x:Key="SourceConverter" Type="{x:Type monitor:VariableValueSource}" />
Radio button bindings:
<RadioButton GroupName="ValueSource"
IsChecked="{Binding Source, Converter={StaticResource SourceConverter}, ConverterParameter=Function}">Function</RadioButton>
This example might be seem a bit lengthy, but its intention should be quite clear.
It uses 3 Boolean properties in the ViewModel called, FlagForValue1, FlagForValue2 and FlagForValue3.
Each of these 3 properties is backed by a single private field called _intValue.
The 3 Radio buttons of the view (xaml) are each bound to its corresponding Flag property in the view model. This means the radio button displaying "Value 1" is bound to the FlagForValue1 bool property in the view model and the other two accordingly.
When setting one of the properties in the view model (e.g. FlagForValue1), its important to also raise property changed events for the other two properties (e.g. FlagForValue2, and FlagForValue3) so the UI (WPF INotifyPropertyChanged infrastructure) can selected / deselect each radio button correctly.
private int _intValue;
public bool FlagForValue1
{
get
{
return (_intValue == 1) ? true : false;
}
set
{
_intValue = 1;
RaisePropertyChanged("FlagForValue1");
RaisePropertyChanged("FlagForValue2");
RaisePropertyChanged("FlagForValue3");
}
}
public bool FlagForValue2
{
get
{
return (_intValue == 2) ? true : false;
}
set
{
_intValue = 2;
RaisePropertyChanged("FlagForValue1");
RaisePropertyChanged("FlagForValue2");
RaisePropertyChanged("FlagForValue3");
}
}
public bool FlagForValue3
{
get
{
return (_intValue == 3) ? true : false;
}
set
{
_intValue = 3;
RaisePropertyChanged("FlagForValue1");
RaisePropertyChanged("FlagForValue2");
RaisePropertyChanged("FlagForValue3");
}
}
The xaml looks like this:
<RadioButton GroupName="Search" IsChecked="{Binding Path=FlagForValue1, Mode=TwoWay}"
>Value 1</RadioButton>
<RadioButton GroupName="Search" IsChecked="{Binding Path=FlagForValue2, Mode=TwoWay}"
>Value 2</RadioButton>
<RadioButton GroupName="Search" IsChecked="{Binding Path=FlagForValue3, Mode=TwoWay}"
>Value 3</RadioButton>
Sometimes it is possible to solve it in the model like this:
Suppose you have 3 boolean properties OptionA, OptionB, OptionC.
XAML:
<RadioButton IsChecked="{Binding OptionA}"/>
<RadioButton IsChecked="{Binding OptionB}"/>
<RadioButton IsChecked="{Binding OptionC}"/>
CODE:
private bool _optionA;
public bool OptionA
{
get { return _optionA; }
set
{
_optionA = value;
if( _optionA )
{
this.OptionB= false;
this.OptionC = false;
}
}
}
private bool _optionB;
public bool OptionB
{
get { return _optionB; }
set
{
_optionB = value;
if( _optionB )
{
this.OptionA= false;
this.OptionC = false;
}
}
}
private bool _optionC;
public bool OptionC
{
get { return _optionC; }
set
{
_optionC = value;
if( _optionC )
{
this.OptionA= false;
this.OptionB = false;
}
}
}
You get the idea.
Not the cleanest thing, but easy.
Aviad P.s answer works very well. However I had to change the equality check to compare strings in OnRadioBindingChanged otherwise the enum was compared to the string value and no radio button was checked initially.
private static void OnRadioBindingChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
BindableRadioButton rb = (BindableRadioButton) d;
if (rb.RadioValue.Equals(e.NewValue?.ToString()))
{
rb.SetCurrentValue(IsCheckedProperty, true);
}
}
Answer 2.0
While I provided the answer above that is quite powerful being a re-templated ListBox, it's still far from ideal for simple radio buttons. As such, I've come up with a much-simpler solution that instead uses a MarkupExtension subclass that implements IValueConverter and which is armed with the power of Binding.DoNothing, the magic sauce that makes two-way bindings work.
Binding to Scalar Values
Let's take a look at the converter itself for binding to scalars...
public class RadioButtonConverter : MarkupExtension, IValueConverter {
public RadioButtonConverter(object optionValue)
=> OptionValue = optionValue;
public object OptionValue { get; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value.Equals(OptionValue);
public object ConvertBack(object isChecked, Type targetType, object parameter, CultureInfo culture)
=> (bool)isChecked
? OptionValue
: Binding.DoNothing; // Only send value back if this is the checked option, otherwise do nothing
public override object ProvideValue(IServiceProvider serviceProvider)
=> this;
}
The magic sauce is in the use of Binding.DoNothing in the ConvertBack function. Since with RadioButton controls, there can only be one active option per 'group' (i.e. only one with IsChecked set to true), we ensure only that RadioButton's binding updates the source. Those on the other RadioButton instances simply do nothing.
Here's how you use it to bind to an int value as the OP asked (below, 'cv' is the imported namespace where the converter code resides, and the value you pass to the converter is the value that particular RadioButton represents)...
<RadioButton Content="One" IsChecked="{Binding SomeIntProp, Converter={cv:RadioButtonConverter 1}}" />
<RadioButton Content="Two" IsChecked="{Binding SomeIntProp, Converter={cv:RadioButtonConverter 2}}" />
<RadioButton Content="Three" IsChecked="{Binding SomeIntProp, Converter={cv:RadioButtonConverter 3}}" />
Simplifying the Binding
While the above works, that's a lot of repeated code and for 90% of the time, you aren't doing anything special with the binding or converter. As such, let's try to simplify things with a RadioButtonBinding that sets up the converter for you. Here's the code...
public class RadioButtonBinding : Binding {
public RadioButtonBinding(string path, object optionValue)
: base(path)
=> Converter = new RadioButtonConverter(optionValue);
}
With this new binding, the call site is greatly simplified (here, 'b' is the imported namespace where the binding code resides)...
<RadioButton Content="One" IsChecked="{b:RadioButtonBinding SomeIntProp, 1}" />
<RadioButton Content="Two" IsChecked="{b:RadioButtonBinding SomeIntProp, 2}" />
<RadioButton Content="Three" IsChecked="{b:RadioButtonBinding SomeIntProp, 3}" />
Note: Make sure you don't also set the Converter argument or you will defeat the entire point of using this!
Binding to Enum Values
The above example dealt with basic scalars (e.g. 1, 2, 3.) However, what if the value we want to is an enumeration such as the following?
public enum TestEnum {
yes,
no,
maybe,
noIdea
}
The syntax is the same, but at the call-site, we need to be more specific about the value we're binding to making it much more verbose. (For instance, if you try and pass 'yes' by itself, it will be treated as a string, not an enum, so it will fail the equality check.)
Here's the converter version's call-site (here, 'v' is the imported namespace where the enum values reside)...
<RadioButton Content="Yes" IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonConverter {x:Static v:TestEnum.yes}}}" />
<RadioButton Content="No" IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonConverter {x:Static v:TestEnum.no}}}" />
<RadioButton Content="Maybe" IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonConverter {x:Static v:TestEnum.maybe}}}" />
<RadioButton Content="No Idea" IsChecked="{Binding SomeEnumProp, Converter={cv:RadioButtonConverter {x:Static v:TestEnum.noIdea}}}" />
And while simpler, here's the binding version's call-site, better, but still verbose...
<RadioButton Content="Yes" IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.yes}}" />
<RadioButton Content="No" IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.no}}" />
<RadioButton Content="Maybe" IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.maybe}}" />
<RadioButton Content="No Idea" IsChecked="{b:RadioButtonBinding SomeEnumProp, {x:Static v:TestEnum.noIdea}}" />
Enum-Type-Specific variants
If you know you will be binding to a particular enum type on many occasions, you can simplify the above by subclassing the earlier converter and binding to be enum-specific variants.
Below is doing exactly that with TestEnum defined above, like so...
// TestEnum-specific Converter
public class TestEnumConverter : RadioButtonConverter {
public TestEnumConverter(TestEnum optionValue)
: base(optionValue) {}
}
// TestEnum-specific Binding
public class TestEnumBinding : RadioButtonBinding {
public TestEnumBinding(string path, TestEnum value)
: base(path, value) { }
}
And here are the call sites...
<!- Converter Variants -->
<RadioButton Content="Yes" IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter yes}}" />
<RadioButton Content="No" IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter no}}" />
<RadioButton Content="Maybe" IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter maybe}}" />
<RadioButton Content="No Idea" IsChecked="{Binding SomeTestEnumProp, Converter={cv:TestEnumConverter noIdea}}" />
<!- Binding Variants -->
<RadioButton Content="Yes" IsChecked="{b:TestEnumBinding SomeTestEnumProp, yes}" />
<RadioButton Content="No" IsChecked="{b:TestEnumBinding SomeTestEnumProp, no}" />
<RadioButton Content="Maybe" IsChecked="{b:TestEnumBinding SomeTestEnumProp, maybe}" />
<RadioButton Content="No Idea" IsChecked="{b:TestEnumBinding SomeTestEnumProp, noIdea}" />
As you can see, the XAML parser automatically handles the string-to-enum conversion for you making your code much simpler to read. Can't get much simpler than that! :)
Sidenote: One nice thing about the versions where you explicitly specify the enum value in its more-verbose declaration is you get auto-complete for the enum's cases. You don't get that with the enum-type-specific versions that convert the string for you. However, the latter will fail to compile if you use an invalid string value so the tradeoff is brevity vs auto-complete convenience.
I created an attached property based on Aviad's Answer which doesn't require creating a new class
public static class RadioButtonHelper
{
[AttachedPropertyBrowsableForType(typeof(RadioButton))]
public static object GetRadioValue(DependencyObject obj) => obj.GetValue(RadioValueProperty);
public static void SetRadioValue(DependencyObject obj, object value) => obj.SetValue(RadioValueProperty, value);
public static readonly DependencyProperty RadioValueProperty =
DependencyProperty.RegisterAttached("RadioValue", typeof(object), typeof(RadioButtonHelper), new PropertyMetadata(new PropertyChangedCallback(OnRadioValueChanged)));
private static void OnRadioValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is RadioButton rb)
{
rb.Checked -= OnChecked;
rb.Checked += OnChecked;
}
}
public static void OnChecked(object sender, RoutedEventArgs e)
{
if (sender is RadioButton rb)
{
rb.SetCurrentValue(RadioBindingProperty, rb.GetValue(RadioValueProperty));
}
}
[AttachedPropertyBrowsableForType(typeof(RadioButton))]
public static object GetRadioBinding(DependencyObject obj) => obj.GetValue(RadioBindingProperty);
public static void SetRadioBinding(DependencyObject obj, object value) => obj.SetValue(RadioBindingProperty, value);
public static readonly DependencyProperty RadioBindingProperty =
DependencyProperty.RegisterAttached("RadioBinding", typeof(object), typeof(RadioButtonHelper), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnRadioBindingChanged)));
private static void OnRadioBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is RadioButton rb && rb.GetValue(RadioValueProperty).Equals(e.NewValue))
{
rb.SetCurrentValue(RadioButton.IsCheckedProperty, true);
}
}
}
usage :
<RadioButton GroupName="grp1" Content="Value 1"
helpers:RadioButtonHelper.RadioValue="val1" helpers:RadioButtonHelper.RadioBinding="{Binding SelectedValue}"/>
<RadioButton GroupName="grp1" Content="Value 2"
helpers:RadioButtonHelper.RadioValue="val2" helpers:RadioButtonHelper.RadioBinding="{Binding SelectedValue}"/>
<RadioButton GroupName="grp1" Content="Value 3"
helpers:RadioButtonHelper.RadioValue="val3" helpers:RadioButtonHelper.RadioBinding="{Binding SelectedValue}"/>
<RadioButton GroupName="grp1" Content="Value 4"
helpers:RadioButtonHelper.RadioValue="val4" helpers:RadioButtonHelper.RadioBinding="{Binding SelectedValue}"/>

Resources