How to get the selected index of MVVM-bound radiobuttons? - c#

I have an ItemsControl which is bound and set to an observablecollection in my viewmodel:
<ItemsControl ItemsSource="{Binding AwaySelection}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding AwayText}" ></RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now, how to find out which one is clicked? I would like to bind the IsChecked value of each Radiobutton to a single variable in the viewmodel that returns an index to the collection. This would make it very easy for me to directly reference the selected item. Any ideas?

This is how I solved this problem. I wrote an EnumToBool converter for this, like
public class EnumToBoolConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value,
Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (parameter.Equals(value))
return true;
else
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return parameter;
}
#endregion
}
And I've the following enumeration
public enum CompanyTypes
{
Type1Comp,
Type2Comp,
Type3Comp
}
Now, in my Xaml, I'm passing the types as the converter parameter.
<Window x:Class="WpfTestRadioButtons.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTestRadioButtons"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<local:EnumToBoolConverter x:Key="EBConverter"/>
</Window.Resources>
<Grid>
<StackPanel>
<RadioButton IsChecked="{Binding Path=Type,
Converter={StaticResource EBConverter},
ConverterParameter={x:Static local:CompanyTypes.Type1Comp}}" Content="Type1"/>
<RadioButton IsChecked="{Binding Path=Type,
Converter={StaticResource EBConverter},
ConverterParameter={x:Static local:CompanyTypes.Type2Comp}}" Content="Type2"/>
</StackPanel>
</Grid>
</Window>
Now, in your view model, you should have a property (in this case Type), which is of that Enum type.
Like,
public CompanyTypes Type
{
get
{
return _type;
}
set
{
_type = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Type"));
}
}
In this example, you might have noticed that Radiobuttons are static. In your case, as you are listing the radio buttons inside an Item control, you need to bind your ConverterParameter of your RadioButton as well, to the correct type.

In the end, I put the radio buttons into a listview, and bind the isselected property of the listview to the radiobutton one.
link Forum post describing this technique

When use MVVM with radiobutton control exits a problem on method onToggle(), but you can create a radiobutton for that.
public class DataBounRadioButton: RadioButton
{
protected override void OnChecked(System.Windows.RoutedEventArgs e) {
}
protected override void OnToggle()
{
this.IsChecked = true;
}
}
Then add reference to control and Binding a property, in my case IsActive.
<controls:DataBounRadioButton
IsChecked="{Binding IsActive}"/>

Related

Why is this DataTemplate inside a ListView not updating its binding automatically?

I have the following class hierarchy:
namespace WpfBindingProblem
{
public class Top
{
public IList<Mid> MidList { get; } = new ObservableCollection<Mid>();
}
public class Mid
{
public IList<Bot> BotList { get; } = new ObservableCollection<Bot>();
}
public class Bot
{
}
}
And I have this XAML window:
<Window x:Class="WpfBindingProblem.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:WpfBindingProblem"
mc:Ignorable="d"
Title="MainWindow" Height="217.267" Width="333.686">
<Window.DataContext>
<local:Top/>
</Window.DataContext>
<Window.Resources>
<local:TriggersToString x:Key="TriggersToString"/>
</Window.Resources>
<Grid>
<ListView Margin="10" ItemsSource="{Binding MidList}" x:Name="ThatList">
<ListView.Resources>
<DataTemplate DataType="{x:Type local:Mid}">
<TextBlock Text="{Binding BotList, Converter={StaticResource TriggersToString}}" />
</DataTemplate>
</ListView.Resources>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add mid" Click="AddMid"/>
<MenuItem Header="Add bot to selected mid" Click="AddBot" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
With these handlers:
namespace WpfBindingProblem
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AddMid(object sender, RoutedEventArgs e)
{
if(DataContext is Top p)
{
p.MidList.Add(new Mid());
}
}
private void AddBot(object sender, RoutedEventArgs e)
{
if(ThatList.SelectedItem is Mid c)
{
c.BotList.Add(new Bot());
}
}
}
}
And this converter (as a stand-in for any arbitrary converter):
namespace WpfBindingProblem
{
[ValueConversion(typeof(IList<Bot>), typeof(string))]
public class TriggersToString : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is IList<Bot> list)
{
return list.Count.ToString();
}
throw new InvalidOperationException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
}
In the window that appears when we run this example, I can right click and choose "Add mid" so an instance of Mid is added to the Top data context, and the list view is updated accordingly, showing the number 0 (as per the conversion logic)
However, when I click "Add bot to selected mid", an instance of Bot is added to the selected Mid (I can verify this using breakpoints), but the list view is not updated accordingly (I expected 0 to be changed to 1, but the converter is not called again for that particular instance of Mid).
Why does this change not trigger an update of the GUI?
I know I can work around this with some hacks (like setting the data context to null and back, or possibly by invoking explicit updates using dependency properties), but there are two reasons why I'd like to avoid that:
My actual code is more complex than this MCVE and it would look very ugly.
I've already sprinkled all my (actual) classes with all the required the ObservableCollections and the INotifyPropertyChanged interfaces, precisely so that I wouldn't need to perform manual updates — so I feel like automatic updates should happen in this case, unless I've missed something.
Why does this change not trigger an update of the GUI?
Because the source property of the binding (BotList) is not updated. The converter is invoked only when the data bound property is updated.
You could use a MultiBinding as suggested by #Sinatr or you could
bind directly to the Count property of the collection:
<TextBlock Text="{Binding BotList.Count}" />
implement the INotifyPropertyChanged interface in the Mid class and raise the PropertyChanged event for the BotList property whenever an item is added to it. Handle CollectionChanged.
You might also move your convert logic to the view model, bind to a property of this one and also raise the PropertyChanged for it whenever you want the binding to be refreshed.
You can use multi binding:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TriggersToString}">
<Binding Path="BotList" />
<Binding Path="BotList.Count" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
and multi value converter:
public class TriggersToString : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
(values[0] as IList<Bot>)?.Count.ToString(); // first binding
...
}
This way the converter is called whenever either of bindings is updated.

WPF - Bind ComboBox Item Foreground to Its Value

I created a ComboBox listing the colors that System.Windows.Media.Colors predefines, using the approach told in this question: How can I list colors in WPF with XAML?
My XAML code now is:
<Window ...>
<Window.Resources>
<ObjectDataProvider
ObjectInstance="{x:Type Colors}" MethodName="GetProperties" x:Key="ColorList" />
<local:StringToBrushConverter x:Key="FontColorConversions" />
</Window.Resources>
<Grid Background="Black">
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource ColorList}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource FontColorConversions}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
...
</Grid>
</Window>
And besides, please note that I bind SelectedValue to a VM class's FontColor property, which is of string type.
class FontSetting : INotifyPropertyChanged
{
private string _fontColor = "Lavender"; // initial color
public event PropertyChangedEventHandler PropertyChanged;
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
OnPropertyChanged("FontColor");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I set the DataContext of the Window containing this ComboBox to an instance of FontSetting.
So each item in the ComboBox actually display a string representing a certain color now, what I want to do is set an item's Foreground color to that color its content indicates, like this:
Can anyone help? Thanks.
UPDATED:
Since most of the solutions have a converter which converts string to Brush and actually I already have it, now I want to put mine here, as I binded a TextBox's Foreground to FontSetting's FontColor property, so that when you change the ComboBox, the color of that TextBox changes accordingly.
Here is my converter class, and it works fine by now:
class StringToBrushConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = conv.ConvertFromString("Lavender") as SolidColorBrush;
if (null != value)
{
brush = conv.ConvertFromString(value.ToString()) as SolidColorBrush;
}
return brush;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
When I click the ComboBox to open the dropdown list, I got an exception:
CONCLUSION
Amine's solution works, it's my mistake. I explain briefly now, if you bind a ComboBox to System.Windows.Media.Colors like what I am doing, when the item is rendered, the Convert() method of the converter class (which you assign to the binding) is executed, and actually the value passed to Convert() as its first parameter is a Syetem.Windows.Media.Color instance. I made mistake coz I thought it was of string type.
Therefore, in my case I need two converter classes, one converting string to Brush, and the other one converting Color to Brush. So I will keep my own StringToBrush converter and add Amine's ColorToBrush converter.
However, I simplified Amine's implementation a bit:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = SolidColorBrush)conv.ConvertFromString(FontSetting.DEFAULT_FONT_COLOR);
if (null != value)
{
PropertyInfo pi = value as PropertyInfo;
if (null != pi)
{
brush = conv.ConvertFromString(pi.Name) as SolidColorBrush;
}
}
return brush;
}
Moreover, Joe's input is also valuable, put all them together, I can keep the items' color consistent, which is perfect.
You can set Style of ComboBoxItem as bellow :
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240" x:Name="CB"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource converter}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
By using this converter:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
object obj = ((System.Reflection.PropertyInfo)value).GetValue(this,null);
return (SolidColorBrush)new BrushConverter().ConvertFromString(obj.ToString());
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return value;
}
}
Two ways, use a value converter or an intermediate property. The easiest is probably an intermediate property as you are using well structured bindings already for your SelectedItem (but value converters are fun too!).
SelectedValue is bound to FontColor, so in your setter set another value:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
ForegroundColorToDisplay = GetBrushFromColorString(value);
OnPropertyChanged("FontColor");
}
}
public Brush _foregroundColorToDisplay
public Brush ForegroundColorToDisplay
{
get
{
return _foregroundColorToDisplay;
}
set
{
_foregroundColorToDisplay= value;
OnPropertyChanged("ForegroundColorToDisplay");
}
}
or, if you don't want to store it:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
//note it fires two changed events!
OnPropertyChanged("ForegroundColorToDisplay");
OnPropertyChanged("FontColor");
}
}
public Brush ForegroundColorToDisplay
{
get
{
return GetBrushFromColorString(value);;
}
}
You can bind to this new property in your xaml:
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding ForegroundColorToDisplay, Mode=OneWay}"/>
If you are interested, a value converter would work like this:
Create a class in your code behind (or elsewhere if it's going to be used a lot) that implements IValueConverter, takes the string and returns a Brush:
public class StringToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return GetBrushFromString(value as string)
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
Throw new SomeException();
}
}
and use it in your binding in xaml to convert the selectedItem binding to Foreground brush:
<Window.Resources>
<local:StringToBrushConverter x:Key="converter" />
</Window.Resources>
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding FontColor, Mode=OneWay, Converter={StaticResource converter}}"/>

Binding items in ListBox in WP8

In a Windows Phone 8 app,
I have a listbox with 2 TextBlocks and a button.
I have a list of 2 strings and a boolean & I am able to bind the strings to the TextBlocks.
<ListBox Name="ListboxTest">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}" TextWrapping="Wrap"/>
<TextBlock Text="{Binding Value}" TextWrapping="Wrap"/>
<Button />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And this is the C# code to bind to the list box.
public class Detail
{
public string Key { get; set; }
public string Value { get; set; }
public bool check { get; set; }
}
public List<Detail> ClassList = new List<Detail>();
ListboxTest.ItemsSource = ClassList;
I want to display the button only when the boolean value is true.
How do I do it?
Take a look at this. Actually what you really need is a Converter by implementing the IValueConverter. This is also a good example where you could read about it. Bind the boolean value with the visibility property of the button and you are done! ;)
You can use boolean to visibility converter to hide, show button
Here are example:
public class BoolToVisibility : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var boolValue = false;
if (value != null) boolValue = (bool)value;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In app.xaml
<my:BoolToVisibility x:Key="BoolToVisibility"/>
In your data template
<Button Visibility="{Binding Path=YourBoolProperty,Converter={StaticResource BoolToVisibility}}>
Or, you could add this property to the Detail class:
public Visibility ButtonVisibility {
get {
return this.check == true ? Visibility.Visible : Visibility.Collapsed;
}
}
And then just bind the button's Visibility to the ButtonVisibility property without any converters.
<Button Visibility="{Binding ButtonVisibility}" />
Please try those use Triggers.
Various Triggers in windows phone Msdn.
Please use ObservableCollection in WP8 for binding instead of List.
Please make your properties are implemented with INotifyPropertyChanged If your Boolean property is not implemented with inotifypropertychanged the view will not know the value is changed.hence the Data trigger will not work.
Namespace
xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ec="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
x:Class="XXX_XXXX"
<Button Content="My button"
Stretch="None"
HorizontalAlignment="Stretch"
VerticalAlignment="Top">
<interactivity:Interaction.Triggers>
<ec:DataTrigger Binding="{Binding Check}" Value="True">
<ec:ChangePropertyAction PropertyName="Visibility">
<ec:ChangePropertyAction.Value>
<Visibility>Visible</Visibility>
</ec:ChangePropertyAction.Value>
</ec:ChangePropertyAction>
</ec:DataTrigger>
<ec:DataTrigger Binding="{Binding Check}" Value="False">
<ec:ChangePropertyAction PropertyName="Visibility">
<ec:ChangePropertyAction.Value>
<Visibility>Collapsed</Visibility>
</ec:ChangePropertyAction.Value>
</ec:ChangePropertyAction>
</ec:DataTrigger>
</interactivity:Interaction.Triggers>
</Button>
Note Answered from phone syntax may not be correct

Toggling a bound radio button by code

WPF / Caliburn micro related question.
I have 4 radio buttons that I am binding the IsChecked property to ArrowType, which has a type of LogicArrowEnum enumeration that I created.
Radiobuttons use a converter to properly assign the relevant enumeration to the ArrowType property based on which button has been clicked.
XAML:
<Window.Resources>
<my:EnumToBoolConverter x:Key="EBConverter"/>
</Window.Resources>
...
<RadioButton IsChecked="{Binding ArrowType,
Converter={StaticResource EBConverter},
ConverterParameter={x:Static my:LogicArrowEnum.ARROW}}"
Name="LogicArrow"
Style="{StaticResource {x:Type ToggleButton}}"
Width="50"
<TextBlock Text="Arrow"/>
</RadioButton>
<RadioButton IsChecked="{Binding ArrowType,
Converter={StaticResource EBConverter},
ConverterParameter={x:Static my:LogicArrowEnum.ASSIGN}}"
Name="LogicAssign"
Style="{StaticResource {x:Type ToggleButton}}"
Width="50"
<TextBlock Text="Assign"/>
</RadioButton>
<RadioButton
IsChecked="{Binding ArrowType,
Converter={StaticResource EBConverter},
ConverterParameter={x:Static my:LogicArrowEnum.IF}}"
Name="LogicIf"
Style="{StaticResource {x:Type ToggleButton}}"
Width="50"
<TextBlock Text="If" />
Code:
public class EnumToBoolConverter : IValueConverter
{
public object Convert(object value,
Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (parameter.Equals(value))
return true;
else
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return parameter;
}
}
public enum LogicArrowEnum
{
ARROW = 1,
ASSIGN = 2,
IF = 3,
IF_ELSE = 4
}
public LogicArrowEnum ArrowType
{
get { return arrowType; }
set
{
arrowType = value;
NotifyOfPropertyChange(() => ArrowType);
}
}
The code works wonderfully - user clicks a button, the ArrowType property is properly bound.
I'd like to make this work backwards as well. For example, if I set the ArrowType property to LogicArrowEnum.ASSIGN via code, the UI should show that the Assign button has been toggled. For some reason, this does not work as expected. Inside the set method of property, whenever I assign the ArrowType property to an arbitrary enumeration, the private field of arrowType is first assigned as the value that I want it to, but then as soon as the code reaches NotifyOfPropertyChange method, it makes it enter the set method again but then resets the arrowType private field to the previously toggled button.
Is this a Caliburn Micro related bug or some WPF related bug? How can I fix this?
There is no relation to Caliburn.Micro.
Check out this: How to bind RadioButtons to an enum?
Take a converter from Scott answer and it will work fine.

Expressions in Databinding... possible? How to?

I have a collection databound to a ListBox. What I would like to do is show some UI based on whether or not some property of the member of the collection exists.
E.g.:
public class Widget
{
public string foo;
public string bar;
}
public ObservableCollection<Widget> Stuff;
XAML:
<ListBox ItemsSource="{Binding Stuff}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding foo}"
Visiblity="{Binding
(foo != null ? Visibility.Visible : Visibility.Collapsed)
}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note the Visibility attribute on the TextBlock. Clearly this isn't supported, but it should give you an idea of what I want to do.
One possible solution is that I could add a property to widget that looks like this:
public Visibility has_foo;
And then:
... Visibility="{Binding has_foo}" ...
But it seems awkward to have to generate these additional properties.
I suspect there is a much better way. Is there? How would you do it?
Thanks.
Create a value converter. Something like
public class NullToVisibilityConverter : 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();
}
}
Then add it something like
<YourUserControl.Resources>
<NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</YourUserControl.Resources>
<ListBox ItemsSource="{Binding Stuff}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding foo}"
Visiblity="{Binding foo,
Converter={StaticResource NullToVisibilityConverter}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Obviously I've not implemented ConvertBack (not really sure if you will be able to convert back) but you shouldn't need it in this instance.

Categories

Resources