WPF radio button binding - getting an error in this code here? - c#

Trying to get radiobuttons binding working but getting a run time error with the code below. Want the radio buttons to act such that only one can be selected at a time, and that they bind correctly in a 2 way fashion. Error text is.
"The invocation of the constructor on
type 'testapp1.MainWindow' that
matches the specified binding
constraints threw an exception"
<Window x:Class="testapp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:testapp1" Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<l:EnumBooleanConverter x:Key="enumBooleanConverter" />
</Grid.Resources>
<StackPanel >
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>
<Label Content="{Binding Path=VeryLovelyEnum}" Height="28" Name="label1" />
</StackPanel>
</Grid>
</Window>
And code:
namespace testapp1
{
public partial class MainWindow : Window
{
public TestModel _model;
public MainWindow()
{
InitializeComponent();
InitializeComponent();
_model = new TestModel();
this.DataContext = _model;
}
}
public enum MyLovelyEnum
{
FirstSelection,
TheOtherSelection,
YetAnotherOne
};
public class TestModel : DependencyObject
{
public MyLovelyEnum VeryLovelyEnum
{
get { return (MyLovelyEnum)GetValue(VeryLovelyEnumProperty); }
set { SetValue(VeryLovelyEnumProperty, value); }
}
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(0));
// from http://stackoverflow.com/questions/397556/wpf-how-to-bind-radiobuttons-to-an-enum
public class EnumBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return DependencyProperty.UnsetValue;
object parameterValue = Enum.Parse(value.GetType(), parameterString);
return parameterValue.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
return Enum.Parse(targetType, parameterString);
}
#endregion
}
}
The button & label is there from a previous test I did - I just left in...

The following declaration uses an integer instead of a default enum-value. Could be your instantiation problem...
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(0));
Try something like...
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(MyLovelyEnum.FirstSelection));

Related

How to bind UI color attribute with class property

I have a user control which is an ellipse that acts like a "led". I want to bind its "Fill" to a boolean property (State).
I used for that a boolean to Color converter.
here is the user control I did:
<UserControl x:Class="Sol.Components.Led"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
...
xmlns:conv="clr-namespace:Sol.Converters"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Ellipse Fill="{Binding Converter={StaticResource BoolToColor}}" StrokeThickness="3" Stroke="Gray"/>
</Grid>
</UserControl>
also the converter is not recognised in the user control! I did it like this
public class BoolToColor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
if ((bool)value == true)
return Colors.Green; // to replace with onColor
else
return Colors.Red; // to replace with offColor
}
return Colors.LightGray;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Color)
{
if ((Color)value == Colors.Green) // to compare with onColor
{
return true;
}
}
return false;
}
}
I used a window to include 4 user contols:
<Window x:Class="Sol.Menu.Leds"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:led="clr-namespace:Sol.Components"
xmlns:conv="clr-namespace:Sol.Converters"
Title="Leds" Height="300" Width="300">
<Window.Resources>
<conv:BoolToColor x:Key="BoolToColor" />
</Window.Resources>
<Grid>
...
<led:Led Grid.Column="0" Grid.Row="0" x:Name="led1" State="False"/>
<led:Led Grid.Column="0" Grid.Row="1" x:Name="led2" State="False"/>
<led:Led Grid.Column="1" Grid.Row="0" x:Name="led3" State="False"/>
<led:Led Grid.Column="1" Grid.Row="1" x:Name="led4" State="False"/>
</Grid>
</Window>
and the used control class :
public partial class Led : UserControl
{
private bool state;
public bool State
{
get { return state; }
set { state = value; }
}
private Color onColor;
public Color OnColor
{
get { return onColor; }
set { onColor = value; }
}
private Color offColor;
public Color OffColor
{
get { return offColor; }
set { offColor = value; }
}
public Led()
{
InitializeComponent();
}
}
this is works without binding and the window shows 4 ellipses, but I am unable to change the color dynamically (from the code bedhind).
any help to fix the binding?
Try to bind to the State property of the UserControl:
<Ellipse Fill="{Binding Path=State,
RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource BoolToColor}}" StrokeThickness="3" Stroke="Gray"/>
You should also return a Brush instead of a Color from your converter:
public class BoolToColor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
if ((bool)value == true)
return Brushes.Green; // to replace with onColor
else
return Brushes.Red; // to replace with offColor
}
return Brushes.LightGray;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Brush)
{
if ((Brush)value == Brushes.Green) // to compare with onColor
{
return true;
}
}
return false;
}
}
You need to implement PropertyChanged so the UI knows a property has been changed.
Read here how it should be done: https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
Fill, like all other UI "color" properties, is actually a Brush value, not a Color
Change your converter to return Brushes.Red / Brushes.Green.

IsEnabled property cannot be binded to a DependencyProperty and IValueConverter

I have the following code:
XAML code:
<Window x:Class="combobinding.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:combobinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<local:EnumConverter x:Key="isEnabledConverter" />
</Window.Resources>
<Grid>
<TextBox Text="Hello" IsEnabled="{Binding SectionTitle, Converter={StaticResource isEnabledConverter}}" />
</Grid>
</Window>
C# Code
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty SectionTitleProperty =
DependencyProperty.Register(nameof(SectionTitle),
typeof(SectionTitle),
typeof(MainWindow));
public SectionTitle SectionTitle
{
get { return (SectionTitle)GetValue(SectionTitleProperty); }
set { SetValue(SectionTitleProperty, value); }
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
SectionTitle = SectionTitle.TitleBlock;
}
}
public enum SectionTitle
{
Normal,
TitleBlock
}
public class EnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var sectionType = (SectionTitle)value;
if (sectionType == SectionTitle.Normal)
return true;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
I would expect that the EnumConverter would be called as I am setting the DependencyProperty SectionTitle and any breakpoint inside the method will be hit.
However this doesn't seem to be the case; and the IsEnabled property is not being binded to SectionTitle as I wish.
What's wrong with this code?
The problem is the DataContext. The binding does not find its target.
You can set the context in the declaration of the window. Add this to the Window tag in your XAML:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Define Name property on your Window with Name="MyWindow", then use it in your binding like this:
<TextBox Text="Hello" IsEnabled="{Binding ElementName=MyWindow, Path=SectionTitle, Converter={StaticResource isEnabledConverter}}" />
You need to set the DataContext of your MainWindow. You can esaily do this inside the constructor:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
...

Odd behavior when trying to change a bound RadioButton in WPF

I've bound two radio buttons in my Child window to an Enum in my ViewModel which is constructed in the Main window. The binding works as expected but I have noticed a very odd behavior which I can't solve. I have provided all the code here so you can reconstruct the problem easily for yourself.
Here are the steps to see this odd behavior:
Click on the button in the MainWindow
The ChildWindow opens and the RadioButton is set to User
Choose Automatic and then Choose User again
Close the ChildWindow and reopen it again! Try to change the RadioButton to Automatic. It won't change!
<Window x:Class="RadioButtonBinding.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">
<Button Content="Display Child Window" Click="DisplayChildWindow"/>
</Window>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var viewModel = new ViewModel();
DataContext = viewModel;
}
private void DisplayChildWindow(object sender, RoutedEventArgs e)
{
var win = new ChildWindow {DataContext = (ViewModel) DataContext};
win.ShowDialog();
}
}
<Window x:Class="RadioButtonBinding.ChildWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:radioButtonBinding="clr-namespace:RadioButtonBinding"
Title="ChildWindow" Height="300" Width="300">
<Window.Resources>
<radioButtonBinding:EnumBooleanConverter x:Key="EnumBooleanConverter"/>
</Window.Resources>
<StackPanel>
<RadioButton Content="Automatic"
GroupName="CalcMode"
IsChecked="{Binding Path=CalcMode,
Converter={StaticResource EnumBooleanConverter},
ConverterParameter={x:Static radioButtonBinding:CalcMode.Automatic}}"/>
<RadioButton Content="Custom"
GroupName="CalcMode"
IsChecked="{Binding Path=CalcMode,
Converter={StaticResource EnumBooleanConverter},
ConverterParameter={x:Static radioButtonBinding:CalcMode.User}}"/>
</StackPanel>
</Window>
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private CalcMode calcMode = CalcMode.User;
public CalcMode CalcMode
{
get { return calcMode; }
set
{
calcMode = value;
RaisePropertyChanged("CalcMode");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class EnumBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var paramEnum = parameter as Enum;
var valueEnum = value as Enum;
return Equals(paramEnum, valueEnum);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var parameterEnum = parameter as Enum;
if (parameterEnum == null)
return DependencyProperty.UnsetValue;
return parameterEnum;
}
}
public enum CalcMode : byte
{
Automatic,
User,
}
UPDATE:
I suspect it must be the Converter but I don't know why? It just falls into a loop.
EDIT
What about converting the enum to bool as follows?
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null || !(bool)value)
return DependencyProperty.UnsetValue;
var parameterEnum = parameter as Enum;
return parameterEnum;
}

Cannot bind DependcyProperty of Model from collection in WPF

In WPF project I use MVVM pattern.
I try to bind an item in a collection to UserControl but everything gets default value of DependcyProperty.
The Window xaml:
<ListView VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding Sessions}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="DebugTextBlock" Background="Bisque"
Text="{Binding Connection}"/>
<usercontrol:SessionsControl Model="{Binding Converter=
{StaticResource DebugConverter}}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Where Sessions is
private ObservableCollection<SessionModel> _sessions;
public ObservableCollection<SessionModel> Sessions
{
get { return _sessions; }
set
{
if (Equals(value, _sessions)) return;
_sessions = value;
OnPropertyChanged("Sessions");
}
}
The SessionModel:
public class SessionModel:ViewModelBase
{
private string _connection;
public string Connection
{
get { return _connection; }
set
{
if (value == _connection) return;
_connection = value;
OnPropertyChanged("Connection");
}
}
}
In the SessionsControl I create DependencyProperty:
//Dependency Property
public static readonly DependencyProperty ModelProperty =
DependencyProperty.Register("Model", typeof(SessionModel),
typeof(SessionsControl), new PropertyMetadata(new SessionModel("default_from_control")));
// .NET Property wrapper
public SessionModel Model
{
get { return (SessionModel)GetValue(ModelProperty); }
set { if (value != null) SetValue(ModelProperty, value); }
}
and use this xaml to display connection in form:
<TextBlock Name="DebugControlTextBlock" Background="Gray" Text="{Binding Connection}"/>
So, when I run application
var windowModel = new WindowsModel();
var window = new SessionWindow(windowModel);
window.ShowDialog();
I always get default_from_control value in DebugControlTextBlock, but in DebugTextBlock get the_real_connection
Even if I set breakpoint in DebugConverter I see that value is default.
The DebugConverter is simply wrapper to check correct binding:
public class DebugConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("DebugConverter: " + (value!=null?value.ToString():"null"));
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
See solution on github.
So, what happend when I Binding model to DependcyProperty?
I would suggest that you try to make you Sessions property a DependencyProperty too. Otherwise you might have to manually Raise PropertyChanged for Sessions property.

BindingExpression path error for property in ViewModel

I have the below visibility binding that is throwing:
System.Windows.Data Error: 40 : BindingExpression path error: 'Vis'
property not found on 'object' ''LoginViewModel' (HashCode=22943289)'.
BindingExpression:Path=Vis; DataItem='LoginViewModel'
(HashCode=22943289); target element is 'Login' (Name=''); target
property is 'Visibility' (type 'Visibility')
Can't see what I've done wrong, the property does exist in the MainViewModel. Maybe I'm going about showing and hiding this the wrong way.
<Window x:Class="Bt.MainWindow"
xmlns:vm="clr-namespace:Bt"
xmlns:ctrls="clr-namespace:Bt.Controls">
<Window.DataContext>
<vm:MainViewModel x:Name="MWin" />
</Window.DataContext>
<Grid>
<ctrls:Login Visibility="{Binding Vis}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></ctrls:Login>
</Grid>
</Window>
ViewModel:
namespace Bt
{
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Vis = "Collapsed";
}
private string _vis = "Collapsed";
public string Vis
{
get { return _vis; }
set
{
_vis = value;
RaisePropertyChanged("Vis");
}
}
}
}
[EDIT] Capturing inside the User Control, when the User Control's visibility is changed in the Main window.
I realize that the converter is not being called correctly, so may need some help there as well. As for the rest hopefully you can see what I'm trying to achieve.
View:
<UserControl x:Class="Bt.Controls.Login"
xmlns:app="clr-namespace:Bt"
xmlns:viewmodel="clr-namespace:Bt.Controls"
mc:Ignorable="d"
Visibility="{Binding Visi,Converter={StaticResource BooleanToVisibilityConverter}}"
>
</UserControl>
View Model:
namespace Bt.Controls
{
class LoginViewModel : INotifyPropertyChanged
{
public LoginViewModel(){}
private bool _visi = true;
public bool Visi
{
get { return _visi; }
set
{
_visi = value;
RaisePropertyChanged("Visi");
MessageBox.Show("Visi set");
reset_timer(_visi);
}
}
}
[ValueConversion(typeof(bool), typeof(Visibility))]
public class VisibilityConverter : IValueConverter
{
public const string Invert = "Invert";
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(Visibility))
throw new InvalidOperationException("The target must be a Visibility.");
bool? bValue = (bool?)value;
if (parameter != null && parameter as string == Invert)
bValue = !bValue;
return bValue.HasValue && bValue.Value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return ((value is Visibility) && (((Visibility)value) == Visibility.Visible));
}
#endregion
}
}
As evident from error BindingEngine is looking for property Vis in LoginViewModel and not in MainViewModel. (You must have set DataContext for your Login UserControl to LoginViewModel).
You need to get Window's DataContext which you can get using RelativeSource:
<ctrls:Login Visibility="{Binding DataContext.Vis,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
Also you should create Vis as bool and use BooleanToVisibility converter in your binding.
<Grid>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="BooelanToVisibilityConverter"/>
</Grid.Resources>
<ctrls:Login Visibility="{Binding DataContext.Vis,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window}}",
Converter={StaticResource BooelanToVisibilityConverter}
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</Grid>
I would discourage use of Visibility property in ViewModel because it's View thing which should not be there in ViewModel. Having bool is perfectly fine which you can always convert using converter.
Keep Vis as type of Visibility
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
}
private Visibility _vis = Visibility.Collapsed;
public Visibility Vis
{
get { return _vis; }
set
{
_vis = value;
RaisePropertyChanged("Vis");
}
}
}
Also specify the Source for the binding,
<Window x:Class="Bt.MainWindow"
xmlns:vm="clr-namespace:Bt"
xmlns:ctrls="clr-namespace:Bt.Controls">
<Window.Resources>
<vm:MainViewModel x:Key="MWin" />
</Window.Resources>
<Grid>
<ctrls:Login Visibility="{Binding Vis, RelativeSource={StaticResource MWin}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></ctrls:Login>
</Grid>
</Window>
As Rohit says, you could use BooleanToVisibility instead of changing the property as Visibility..

Categories

Resources