Radiobuttons Ischecked property is not working in wpf mvvm - wpf

I am unable to bind two radiobuttons to my xaml form and IsChecked property with same group names for which I also used nullabletoboolconverters. however, the radiobuttons ischecked property does not get changed in my code(it is not at all hitting the breakpoint, once we hit the first radiobutton after second one) and I am binding ischecked properties of two of them seperately as I need to set the visibility of some other panel on the form based on the radiobuttons property.
the following is my xaml code and later is my viewmodel.cs code for radiobutton properties:
<RadiobuttonSelectedConverter x:Key="CheckedSelection"/>// declaring my converter class in my resource dictionary.
<StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="3" Margin="10,5,0,0">
<RadioButton GroupName="RadiosGroup" IsChecked="{Binding IsRadioButton1,Mode=TwoWay,
Converter={StaticResource CheckedSelection}, ConverterParameter=true}">
First</RadioButton>
<RadioButton GroupName="RadiosGroup"
Margin="40,0,0,0" IsChecked="{Binding IsRadioButton2,Mode=TwoWay,
Converter={StaticResource CheckedSelection}, ConverterParameter=true}">Second</RadioButton>
</StackPanel>
private bool _isRadioButton1;
public bool IsRadioButton1
{
get
{
return _isRadioButton1;
}
set
{
if _isRadioButton1!= value)
{
_isRadioButton1= value;
IsRadioButton2= false;
OnPropertyChanged("IsRadioButton1");
}
}
}
private bool _isRadioButton2;
public bool IsRadioButton2
{
get
{
return _isRadioButton2;
}
set
{
if (_isRadioButton2 != value)
{
_isRadioButton2 = value;
IsRadioButton1= false;
OnPropertyChanged("IsRadioButton2");
}
}
}
the following is my Converter code:
[ValueConversion(typeof(bool?), typeof(bool))]
public class RadiobuttonSelectedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool param = bool.Parse(parameter.ToString());
if (value == null)
{
return false;
}
else
{
return !((bool)value ^ param);
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool param = bool.Parse(parameter.ToString());
return !((bool)value ^ param);
}
}
Someone please help me resolving my issue thanks in advance..

Personally I wouldn't code associated RadioButtons like this at all. Since the selection behavior you want is the same used for a ListBox, I find it easiest to simply use a ListBox that is styled to use RadioButtons for each item.
The code-behind typically will contain
ObservableCollection<string> Options
string SelectedOption
and I will use this style for the ListBox:
<Style x:Key="RadioButtonListBoxStyle" 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">
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}" VerticalAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
The style is applied like this:
<ListBox ItemsSource="{Binding Options}"
SelectedValue="{Binding SelectedOption}"
Style="{StaticResource RadioButtonListBoxStyle}" />
You can also use something else instead of a String for the collection, such as a ChildViewModel, then set your related View based on the current item, which means you don't have to bother with the Visibility of the associated panel in your ViewModel either.
<DockPanel>
<ListBox ItemsSource="{Binding OptionViewModels}"
SelectedValue="{Binding SelectedViewModel}"
Style="{StaticResource RadioButtonListBoxStyle}"
DockPanel.Dock="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Content="{Binding SelectedViewModel}" />
</DockPanel>
But as for your actual problem, I can think of 3 reasons it might be behaving incorrectly.
The first is outlined in Jay's answer: you are setting IsChecked2 to false in your setter for IsChecked1, and IsChecked2 sets IsChecked1 to false in its setter, so the end result is both values are false.
The second is it might be a problem with your converter. You said it a comment it was working correctly without the converter, so I think that may be part of the problem.
And last of all, I believe changing grouped RadioButtons will trigger an IsOptionA.IsSelected = false for the old item and an IsOptionB.IsSelected = true for the newly selected item, and its possible those two are getting crossed somewhere.

A couple of issues here.
You don't need a converter. Make IsRadioButton1 and IsRadioButton2 properties of type bool?, and the TwoWay Binding will suffice, or just leave it as bool if tri-state is not applicable for you.
The logic in your setters appears to be incorrect. In both cases, you are setting the value of the other RadioButton to false, so if IsRadioButton1 is true and you then set IsRadioButton2 to true, the setter will call IsRadioButton = false, and then that setter will call IsRadioButton2 = false. They will both end up being false.
You probably want this to read if(value) IsRadioButton2 = false;
edit
Actually, as I recall, RadioButton is not meant to be bound to a bool property the way a CheckBox is. I think you bind all of the RadioButtons to one property, and use a Converter with a ConverterParameter to set the property. I'll find an example and post in a minute.
Okay, here is one solution, using a derived RadioButton class that behaves purely with bindings: http://blogs.msdn.com/b/mthalman/archive/2008/09/04/wpf-data-binding-with-radiobutton.aspx
Here is a related SO question & answers: MVVM: Binding radio buttons to a view model?

Related

RadioButton IsChecked property gets overridden when changing tabs

I'm sure this behavior is known, but I'm unable to google it. I have following code:
<Window x:Class="ContentControlListDataTemplateKacke.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<TabControl ItemsSource="{Binding Items}">
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
<RadioButton Content="Option1" IsChecked="{Binding Option1}" />
<RadioButton Content="Option2" IsChecked="{Binding Option2}" />
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
The code-behind is simple:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
The ViewModel looks like this:
public class ViewModel : NotificationObject
{
public ViewModel()
{
Items = new ObservableCollection<Item>
{
new Item {Name = "1", Option1 = true},
new Item {Name = "2", Option2 = true}
};
}
public ObservableCollection<Item> Items { get; set; }
}
And an Item like this:
public class Item : NotificationObject
{
public string Name { get; set; }
private bool _option1;
public bool Option1
{
get { return _option1; }
set
{
_option1 = value;
RaisePropertyChanged(() => Option1);
}
}
private bool _option2;
public bool Option2
{
get { return _option2; }
set
{
_option2 = value;
RaisePropertyChanged(() => Option2);
}
}
}
I'm using Prism, so the RaisePropertyChanged raises an PropertyChanged-event. Select the second tab, then the first tab, then the second tab again and voilá, the RadioButtons on the second tab are deselected.
Why?
Another solution apart from Rachels
A colleague of mine just had the idea to bind the GroupName property of the RadioButtons to a unique string of each item. Just change the declaration of the RadioButtons into this:
<RadioButton GroupName="{Binding Name}" Content="Option1" IsChecked="{Binding Option1}" />
<RadioButton GroupName="{Binding Name}" Content="Option2" IsChecked="{Binding Option2}" />
And it works if the Name-property is unique for all items (as its the case for my problem).
WPF is reading all the RadioButtons as part of the same Group, and in a radio button group only one item can be selected at a time.
The load order goes:
Load Tab1
Load Tab1.Radio1. IsChecked = True
Load Tab1.Radio2. IsChecked = True, so set Tab1.Radio2.IsChecked = False
Click Tab 2
Load Tab2
Load Tab2.Radio1. IsChecked = True, so set Tab1.Radio2.IsChecked = False
Load Tab2.Radio2. IsChecked = True, so set Tab2.Radio1.IsChecked = False
By now, Tab2.Radio2 is the only one checked, and all the other Radios have been loaded and Unchecked, so their DataBound values have been updated to false.
Click Tab 1
Load Tab1.Radio1. IsChecked = False
Load Tab1.Radio2. IsChecked = False
If you Radio buttons are unrelated and can both be checked at once, I would suggest switching to CheckBoxes
If they're meant to be grouped and only one item can be selected at a time, I'd suggest switching to a ListBox drawn with RadioButtons, and only storing the SelectedOption in your ViewModel
Here's the style I typically use for that:
<Style x:Key="RadioButtonListBoxStyle" 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">
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}" VerticalAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
It's used like this:
<ListBox ItemsSource="{Binding Options}"
SelectedValue="{Binding SelectedValue}"
Style="{StaticResource RadioButtonListBoxStyle}" />
I used to have a problem very similar to this one where text was getting cleared when I was switching between views. If I recall correctly the following solution was what worked.
Bind OneWay to the properties and mark these bound properties with an attribute.
Every time you load the view (and hence viewmodel), use reflection on the aforementioned attribute to find the bound properties.
Fire off a PropertyChanged event for each of the properties to update the view correctly.
I think this results from the view loading with default settings and not querying the properties on load since nothing is raising a PropertyChanged event.
Also, it's not part of your question, but you can set the data context in XAML (via the DataContext property in Window) directly so that Visual Studio doesn't have to have an explicit codebehind file.

WPF DataGrid cell string format as a style or template

I tried several ways to factor out the format of a cell in a WPF DataGrid (the one provided with .NET 4):
a data converter,
the "StringFormat" binding property within a style,
the "StringFormat" binding property within a data template.
I will describe my attempts as it might help someone else, and I hope someone can give me advice to improve on these solutions. Note that I am fairly new to WPF...
The expected behaviour is that the cell string is formatted to something specific like "1,234,567" for display, but it should be formatted as "1234567" (its default formatting) when editing the cell. When I tried to use a data converter, I did not find a way to use the default formatting when editing, so I focused my energy on styles and templates.
With a style, the definition of the DataGridTextColumn goes like this:
<DataGridTextColumn Header="Price (Style)" SortMemberPath="BILL_PRICE">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding Path=BILL_PRICE, StringFormat={}{0:N0}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding Path=BILL_PRICE}"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
The behaviour is exactly what is expected. However, I cannot factor out this style and use it several times, because of the binding. To solve the factoring problem, I used a DataGridTemplateColumn and data templates. Here is my DataGridTemplateColumn definition:
<DataGridTemplateColumn Header="Price (Template)" SortMemberPath="BILL_PRICE">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding BILL_PRICE}" Template="{StaticResource CurrencyCellControlTemplate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ContentControl Content="{Binding BILL_PRICE, Mode=TwoWay}" Template="{StaticResource CurrencyCellEditingControlTemplate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
And the ControlTemplate definition:
<ControlTemplate x:Key="CurrencyCellControlTemplate" TargetType="ContentControl">
<TextBlock Margin="2,0,2,0" Padding="0" TextAlignment="Right">
<TextBlock.Text>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content" StringFormat="{}{0:N0}"/>
</TextBlock.Text>
</TextBlock>
</ControlTemplate>
<ControlTemplate x:Key="CurrencyCellEditingControlTemplate" TargetType="ContentControl">
<TextBox Padding="0" BorderThickness="0" TextAlignment="Right">
<TextBox.Text>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content"/>
</TextBox.Text>
</TextBox>
</ControlTemplate>
Using data templates solves the initial problem of factoring out DataGrid cell formatting, but using a control template brings up ergonomic and visual problems. For instance, the double-tab navigation caused by the control template (discussed in many other places), and the look of the editing text box (which I try to fix with border thickness, padding and the other property settings.)
Specific questions related to this issue are :
Can a data converter be used to format the string for display and use the default formatting for edition?
Is it possible to factor out the DataGridTextColumn style while still being able to specify the binding source?
Is there a way to use a DataGridTemplateColumn but simply make it look and feel like a DataGridTextColumn?
Make your own custom DataGridTextColumn and creates bindings to assign to the Element and the EditingElement (one of them with a converter one of them without).
The converter formats the string to look like a decimal with the commas.
public class MyDataGridTextColumn : DataGridTextColumn
{
Binding formattedBinding;
Binding unformattedBinding;
FormatConverter formatConverter = new FormatConverter();
protected override FrameworkElement
GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem) as TextBlock;
element.SetBinding(TextBlock.TextProperty, GetFormattedTextBinding());
return element;
}
protected override FrameworkElement
GenerateEditingElement (DataGridCell cell, object dataItem)
{
var element = base.GenerateEditingElement(cell, dataItem) as TextBox;
element.SetBinding(TextBox.TextProperty, GetTextBinding());
return element;
}
Binding
GetTextBinding()
{
var binding = (Binding)Binding;
if (binding == null) return new Binding();
unformattedBinding = new Binding
{
Path = binding.Path,
Mode=BindingMode.TwoWay
};
return unformattedBinding;
}
Binding
GetFormattedTextBinding()
{
var binding = (Binding)Binding;
if (binding == null) return new Binding();
formattedBinding = new Binding
{
Path = binding.Path,
Converter = Formatter,
};
return formattedBinding;
}
public FormatConverter
Formatter
{
get { return formatConverter; }
set { formatConverter = value; }
}
}
public class FormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string textvalue = value as string;
if (!string.IsNullOrEmpty(textvalue))
{
decimal decimalvalue = decimal.Parse(textvalue);
var test = String.Format("{0:0,0.00}", decimalvalue);
return test;
}
else
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException("FormatConverter can only be used OneWay.");
}
}
I didn't understand how the formatting string you posted works so I made a simple one, if you want more decimal places just sort it out in the converter.
All you need to do now is place a reference to the namespace in your xaml and then create a column in the datagrid.
Then all should be well
u_u

DataTrigger, Binding to nested properties via TemplatedParent

According to msdn, it should be perfectly legal, and possible, to bind something to a nested property:
<Binding Path="propertyName.propertyName2" .../>
<Binding Path="propertyName.propertyName2.propertyName3" .../>
In my case, it's not so, though...
I have a custom control, MyControl, with a dependency property ViewModel:
public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
"ViewModel", typeof(IViewModel), typeof(MyControl));
public IViewModel ViewModel
{
get { return (IViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
and in the control template, I try to bind to properties in that viewmodel:
<Style TargetType="{x:Type my:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:MyControl}">
<Grid>
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
<Button x:Name="MyButton" Content="Visible by trigger" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.ButtonVisible}" Value="True">
<Setter TargetName="MyButton" Property="Visibility" Value="Visible" />
</DataTrigger>
.../>
In the viewmodel itself, I have a preoperty Text as follow:
public string Text
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("Text");
}
}
public bool ButtonVisible
{
get { return m_buttonVisible; }
set
{
m_buttonVisible = value;
OnPropertyChanged("ButtonVisible"); }
}
I get no bind errors, but things doesn't happend...
Any clues?
Edit
It looks like the bindings work half way. When the text is changed in the editbox, my Text property is set, but if the Text-property is set in code, the ui won't update.
Edit 2
Looks like my first attempt at simplifying the case before posting was a little to successful... As #Erno points out, the code that I posted seems to work OK.
I have looked at the original code some more, and added a trigger to the scenario. The original code uses triggers to show parts of the ui at given conditions. These are also binded to nested properties. I now think that these triggers fail to trigger. I have updated the code. If it still doesn't show whats wrong, I can post a sample application some where.
There is a comma missing:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
EDIT
Add Mode=TwoWay to the binding:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text, Mode=TwoWay}"/>
EDIT2
Got it! I could reproduce and fix it.
Replace the TemplatedParent with Self in the binding.
Read this explanation

How to display default text "--Select Team --" in combo box on pageload in WPF?

In a WPF app, in MVP app, I have a combo box,for which I display the data fetched from Database. Before the items added to the Combo box, I want to display the default text such as
" -- Select Team --"
so that on pageload it displays and on selecting it the text should be cleared and the items should be displayed.
Selecting data from DB is happening. I need to display the default text until the user selects an item from combo box.
Please guide me
The easiest way I've found to do this is:
<ComboBox Name="MyComboBox"
IsEditable="True"
IsReadOnly="True"
Text="-- Select Team --" />
You'll obviously need to add your other options, but this is probably the simplest way to do it.
There is however one downside to this method which is while the text inside your combo box will not be editable, it is still selectable. However, given the poor quality and complexity of every alternative I've found to date, this is probably the best option out there.
You can do this without any code behind by using a IValueConverter.
<Grid>
<ComboBox
x:Name="comboBox1"
ItemsSource="{Binding MyItemSource}" />
<TextBlock
Visibility="{Binding SelectedItem, ElementName=comboBox1, Converter={StaticResource NullToVisibilityConverter}}"
IsHitTestVisible="False"
Text="... Select Team ..." />
</Grid>
Here you have the converter class that you can re-use.
public class NullToVisibilityConverter : IValueConverter
{
#region Implementation of IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
And finally, you need to declare your converter in a resource section.
<Converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
Where Converters is the place you have placed the converter class. An example is:
xmlns:Converters="clr-namespace:MyProject.Resources.Converters"
The very nice thing about this approach is no repetition of code in your code behind.
I like Tri Q's answer, but those value converters are a pain to use. PaulB did it with an event handler, but that's also unnecessary. Here's a pure XAML solution:
<ContentControl Content="{Binding YourChoices}">
<ContentControl.ContentTemplate>
<DataTemplate>
<Grid>
<ComboBox x:Name="cb" ItemsSource="{Binding}"/>
<TextBlock x:Name="tb" Text="Select Something" IsHitTestVisible="False" Visibility="Hidden"/>
</Grid>
<DataTemplate.Triggers>
<Trigger SourceName="cb" Property="SelectedItem" Value="{x:Null}">
<Setter TargetName="tb" Property="Visibility" Value="Visible"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
No one said a pure xaml solution has to be complicated. Here's a simple one, with 1 data trigger on the text box. Margin and position as desired
<Grid>
<ComboBox x:Name="mybox" ItemsSource="{Binding}"/>
<TextBlock Text="Select Something" IsHitTestVisible="False">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=mybox,Path=SelectedItem}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
Set IsEditable="True" on the ComboBox element. This will display the Text property of the ComboBox.
I dont know if it's directly supported but you could overlay the combo with a label and set it to hidden if the selection isn't null.
eg.
<Grid>
<ComboBox Text="Test" Height="23" SelectionChanged="comboBox1_SelectionChanged" Name="comboBox1" VerticalAlignment="Top" ItemsSource="{Binding Source=ABCD}" />
<TextBlock IsHitTestVisible="False" Margin="10,5,0,0" Name="txtSelectTeam" Foreground="Gray" Text="Select Team ..."></TextBlock>
</Grid>
Then in the selection changed handler ...
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
txtSelectTeam.Visibility = comboBox1.SelectedItem == null ? Visibility.Visible : Visibility.Hidden;
}
Based on IceForge's answer I prepared a reusable solution:
xaml style:
<Style x:Key="ComboBoxSelectOverlay" TargetType="TextBlock">
<Setter Property="Grid.ZIndex" Value="10"/>
<Setter Property="Foreground" Value="{x:Static SystemColors.GrayTextBrush}"/>
<Setter Property="Margin" Value="6,4,10,0"/>
<Setter Property="IsHitTestVisible" Value="False"/>
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
example of use:
<Grid>
<ComboBox x:Name="cmb"
ItemsSource="{Binding Teams}"
SelectedItem="{Binding SelectedTeam}"/>
<TextBlock DataContext="{Binding ElementName=cmb,Path=SelectedItem}"
Text=" -- Select Team --"
Style="{StaticResource ComboBoxSelectOverlay}"/>
</Grid>
Not tried it with combo boxes but this has worked for me with other controls...
ageektrapped blogpost
He uses the adorner layer here to display a watermark.
HappyNomad's solution was very good and helped me eventually arrive at this slightly different solution.
<ComboBox x:Name="ComboBoxUploadProject"
Grid.Row="2"
Width="200"
Height="23"
Margin="64,0,0,0"
ItemsSource="{Binding projectList}"
SelectedValue ="{Binding projectSelect}"
DisplayMemberPath="projectName"
SelectedValuePath="projectId"
>
<ComboBox.Template>
<ControlTemplate TargetType="ComboBox">
<Grid>
<ComboBox x:Name="cb"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}}"
SelectedValue ="{Binding SelectedValue, RelativeSource={RelativeSource TemplatedParent}}"
DisplayMemberPath="projectName"
SelectedValuePath="projectId"
/>
<TextBlock x:Name="tb" Text="Select Item..." Margin="3,3,0,0" IsHitTestVisible="False" Visibility="Hidden"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="cb" Property="SelectedItem" Value="{x:Null}">
<Setter TargetName="tb" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ComboBox.Template>
</ComboBox>
Easiest way is to use CompositeCollection to merge default text and data from database directly in ComboBox e.g.
<ComboBox x:Name="SelectTeamComboBox" SelectedIndex="0">
<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem Visibility="Collapsed">-- Select Team --</ComboBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource ResourceKey=MyComboOptions}}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
And in Resources define StaticResource to bind ComboBox options to your DataContext, because direct binding in CollectionContainer doesn't work correctly.
<Window.Resources>
<CollectionViewSource Source="{Binding}" x:Key="MyComboOptions" />
</Window.Resources>
This way you can define your ComboBox options only in xaml e.g.
<ComboBox x:Name="SelectTeamComboBox" SelectedIndex="0">
<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem Visibility="Collapsed">-- Select Team --</ComboBoxItem>
<ComboBoxItem >Option 1</ComboBoxItem>
<ComboBoxItem >Option 2</ComboBoxItem>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
I would recommend the following:
Define a behavior
public static class ComboBoxBehaviors
{
public static readonly DependencyProperty DefaultTextProperty =
DependencyProperty.RegisterAttached("DefaultText", typeof(String), typeof(ComboBox), new PropertyMetadata(null));
public static String GetDefaultText(DependencyObject obj)
{
return (String)obj.GetValue(DefaultTextProperty);
}
public static void SetDefaultText(DependencyObject obj, String value)
{
var combo = (ComboBox)obj;
RefreshDefaultText(combo, value);
combo.SelectionChanged += (sender, _) => RefreshDefaultText((ComboBox)sender, GetDefaultText((ComboBox)sender));
obj.SetValue(DefaultTextProperty, value);
}
static void RefreshDefaultText(ComboBox combo, string text)
{
// if item is selected and DefaultText is set
if (combo.SelectedIndex == -1 && !String.IsNullOrEmpty(text))
{
// Show DefaultText
var visual = new TextBlock()
{
FontStyle = FontStyles.Italic,
Text = text,
Foreground = Brushes.Gray
};
combo.Background = new VisualBrush(visual)
{
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Center,
Transform = new TranslateTransform(3, 0)
};
}
else
{
// Hide DefaultText
combo.Background = null;
}
}
}
User the behavior
<ComboBox Name="cmb" Margin="72,121,0,0" VerticalAlignment="Top"
local:ComboBoxBehaviors.DefaultText="-- Select Team --"/>
IceForge's answer was pretty close, and is AFAIK the easiest solution to this problem. But it missed something, as it wasn't working (at least for me, it never actually displays the text).
In the end, you can't just set the "Visibility" property of the TextBlock to "Hidden" in order for it to be hidden when the combo box's selected item isn't null; you have to SET it that way by default (since you can't check not null in triggers, by using a Setter in XAML at the same place as the Triggers.
Here's the actual solution based on his, the missing Setter being placed just before the Triggers:
<ComboBox x:Name="combo"/>
<TextBlock Text="--Select Team--" IsHitTestVisible="False">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Setters>
<Setter Property="Visibility" Value="Hidden"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=combo,Path=SelectedItem}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Not best practice..but works fine...
<ComboBox GotFocus="Focused" x:Name="combobox1" HorizontalAlignment="Left" Margin="8,29,0,0" VerticalAlignment="Top" Width="128" Height="117"/>
Code behind
public partial class MainWindow : Window
{
bool clearonce = true;
bool fillonce = true;
public MainWindow()
{
this.InitializeComponent();
combobox1.Items.Insert(0, " -- Select Team --");
combobox1.SelectedIndex = 0;
}
private void Focused(object sender, RoutedEventArgs e)
{
if(clearonce)
{
combobox1.Items.Clear();
clearonce = false;
}
if (fillonce)
{
//fill the combobox items here
for (int i = 0; i < 10; i++)
{
combobox1.Items.Insert(i, i);
}
fillonce = false;
}
}
}
I believe a watermark as mentioned in this post would work well in this case
There's a bit of code needed but you can reuse it for any combobox or textbox (and even passwordboxes) so I prefer this way
I am using an IsNullConverter class in my project and it worked for me.
here is the code for it in c#,create a folder named Converter and add this class in that folder,as the trigger used doesnt supports value for rather than null,and IsNullConverter just do that
public class IsNullConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == null);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("IsNullConverter can only be used OneWay.");
}
}
add the namespace in xaml file like this.
xmlns:Converters="clr-namespace:TymeSheet.Converter"
means
xmlns:Converters="clr-namespace:YourProjectName.Converter"
use this line below the resources to make it availabe through xaml code
<Converters:IsNullConverter x:Key="isNullConverter" />
here is the xaml code,i used here the trigger so whenever an item is selected in the combobox the visibilty of your text becomes false.
<TextBlock Text="Select Project" IsHitTestVisible="False" FontFamily="/TimeSheet;component/Resources/#Open Sans" FontSize="14" Canvas.Right="191" Canvas.Top="22">
<TextBlock.Resources>
<Converters:IsNullConverter x:Key="isNullConverter"/>
</TextBlock.Resources>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ProjectComboBox,Path=SelectedItem,Converter={StaticResource isNullConverter}}" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
//XAML Code
// ViewModel code
private CategoryModel _SelectedCategory;
public CategoryModel SelectedCategory
{
get { return _SelectedCategory; }
set
{
_SelectedCategory = value;
OnPropertyChanged("SelectedCategory");
}
}
private ObservableCollection<CategoryModel> _Categories;
public ObservableCollection<CategoryModel> Categories
{
get { return _Categories; }
set
{
_Categories = value;
_Categories.Insert(0, new CategoryModel()
{
CategoryId = 0,
CategoryName = " -- Select Category -- "
});
SelectedCategory = _Categories[0];
OnPropertyChanged("Categories");
}
}
A little late but..
A more simple way would be to add a dummy data item to the list with parameter IsDummy=true and make sure it is not HitTestVisable and its hight is 1 pixel (using a Converter) so it wont be seen.
Than just register to SelectionChanged and in it, set the index to the dummy item index.
It works like a charm and this way you don't mess with the style and colors of the ComboBox or your application theme.
InitializeComponent()
yourcombobox.text=" -- Select Team --";
The above code demonstrates the simplest way to achieve it. After window load, declare the text of the combobox, using the .Text property of the combobox. This can be extended to the DatePicker, Textbox and other controls as well.
EDIT: Per comments below, this is not a solution. Not sure how I had it working, and can't check that project.
It's time to update this answer for the latest XAML.
Finding this SO question searching for a solution to this question, I then found that the updated XAML spec has a simple solution.
An attribute called "Placeholder" is now available to accomplish this task. It is as simple as this (in Visual Studio 2015):
<ComboBox x:Name="Selection" PlaceholderText="Select...">
<x:String>Item 1</x:String>
<x:String>Item 2</x:String>
<x:String>Item 3</x:String>
</ComboBox>
I did it before binding the combobox with data from database in codebehind like this -
Combobox.Items.Add("-- Select Team --");
Combobox.SelectedIndex = 0;
Put a label on top of the combobox.
Bind the content of the label to to the combobox Text property.
Set the opacity of the combobox to zero , Opacity=0.
Write default text in the combobox Text property
<ComboBox Name="cb"
Text="--Select Team--" Opacity="0"
Height="40" Width="140" >
<ComboBoxItem Content="Manchester United" />
<ComboBoxItem Content="Lester" />
</ComboBox>
</Grid>
This is old, but here's my idea in kind of MVVM style. I'm using Stylet MVVM framework.
This is View:
<UserControl x:Class="ComboBoxWithPlaceholderTextView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
>
<Grid>
<ComboBox
ItemsSource="{Binding ItemsSource}"
SelectedItem="{Binding SelectedItem}"
DropDownOpened="{s:Action DropDownOpened}"
DropDownClosed="{s:Action DropDownClosed}"
IsDropDownOpen="{Binding IsDropDownOpened}"
/>
</Grid>
and then in ViewModel
public class ComboBoxWithPlaceholderTextViewModel : Screen
{
private List<string> _itemsSource;
private string _placeholderText;
private string _selectedItem;
private bool _isDropDownOpened;
public bool IsDropDownOpened
{
get => _isDropDownOpened;
set
{
if (value == _isDropDownOpened)
{
return;
}
SetAndNotify(ref _isDropDownOpened, value);
}
}
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
SetAndNotify(ref _selectedItem, value);
}
}
public string PlaceholderText
{
get { return _placeholderText; }
set
{
if (value == _placeholderText)
{
return;
}
SetAndNotify(ref _placeholderText, value);
}
}
public List<string> ItemsSource
{
get { return _itemsSource; }
set
{
SetAndNotify(ref _itemsSource, value);
if (!IsDropDownOpened && (string.IsNullOrEmpty(SelectedItem) || !SelectedItem.Equals(PlaceholderText)))
{
ItemsSource.Insert(0, PlaceholderText);
SelectedItem = ItemsSource[0];
}
}
}
public void DropDownOpened()
{
ItemsSource.RemoveAt(0);
SelectedItem = null;
}
public void DropDownClosed()
{
if (SelectedItem is null)
{
ItemsSource.Insert(0, PlaceholderText);
SelectedItem = ItemsSource[0];
}
}
}
In this way I don't have to care if text will escape combo, but I have to care if placeholder text is selected.
Only set the IsEditable attribute to true
<ComboBox Name="comboBox1"
Text="--Select Team--"
IsEditable="true" <---- that's all!
IsReadOnly="true"/>
I know this is semi old but what about this way:
<DataTemplate x:Key="italComboWM">
<TextBlock FontSize="11" FontFamily="Segoe UI" FontStyle="Italic" Text="--Select an item--" />
</DataTemplate>
<ComboBox EmptySelectionBoxTemplate="{StaticResource italComboWM}" />

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