Bind two checboxes to a nullable bool property in MVVM - c#

I try to connect two checkboxes to my ViewModel. Their behavior is like a radiobutton (exclusive) and TheeState. So both not checked or one of them checked
At the moment I am doing the job like that:
<dxlc:LayoutGroup>
<dxlc:LayoutItem Label="with errors">
<CheckBox IsChecked="{Binding OnlyMusicWithErrorsChecked}"></CheckBox>
</dxlc:LayoutItem>
<dxlc:LayoutItem Label="without errors">
<CheckBox IsChecked="{Binding OnlyMusicWithoutErrorsChecked}"></CheckBox>
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
und ViewModel:
private bool _onlyMusicWithErrorsChecked;
public bool OnlyMusicWithErrorsChecked
{
get { return _onlyMusicWithErrorsChecked; }
set
{
SetProperty(ref _onlyMusicWithErrorsChecked, value, () => OnlyMusicWithErrorsChecked);
if (OnlyMusicWithErrorsChecked)
OnlyMusicWithoutErrorsChecked = false;
RaisePropertyChanged("AdditionalCriteriaHeader");
if (!_filteringData)
SelectData();
}
}
private bool _onlyMusicWithoutErrorsChecked;
public bool OnlyMusicWithoutErrorsChecked
{
get { return _onlyMusicWithoutErrorsChecked; }
set
{
SetProperty(ref _onlyMusicWithoutErrorsChecked, value, () => OnlyMusicWithoutErrorsChecked);
if (OnlyMusicWithoutErrorsChecked)
OnlyMusicWithErrorsChecked = false;
RaisePropertyChanged("AdditionalCriteriaHeader");
if (!_filteringData)
SelectData();
}
}
The question is: can I use only one property nullable bool to do this job?

You can bind both CheckBoxes to the same property OnlyMusicWithErrorsChecked, and in the second CheckBox add a converter that inverts the property's value:
<CheckBox IsChecked="{Binding OnlyMusicWithErrorsChecked, Converter={StaticResource Inverter}}"></CheckBox>
This converter would look somewhat like:
public class Inverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
return !((bool)value);
else // Fallback
return false;
}
}
EDIT: If you want to build a three-state-solution with only one bindable property, you'll need two converters (or one that can be parameterized):
public class MyConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty InvertProperty = DependencyProperty.Register(
"Invert", typeof (bool), typeof (MyConverter), new PropertyMetadata(default(bool)));
public bool Invert
{
get { return (bool) GetValue(InvertProperty); }
set { SetValue(InvertProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var val = (bool?) value;
switch (val)
{
case true:
return Invert;
break;
case false:
return !Invert;
break;
case null:
return false; // None of the checkboxes shall be active
break;
}
// Fallback
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var val = (bool)value;
switch (val)
{
case true:
return Invert;
break;
case false:
return null;
break;
}
// Fallback
return false;
}
}
The Invert property is set to false for the first checkbox, and true for the second one:
<Window.Resources>
<local:MyConverter x:Key="Converter" Invert="False"/>
<local:MyConverter x:Key="Inverter" Invert="True"/>
</Window.Resources>
Now you can use these two converter instances to bind the checkboxes to the same property:
<CheckBox IsChecked="{Binding MyProperty, Converter={StaticResource Converter}}" />
<CheckBox IsChecked="{Binding MyProperty, Converter={StaticResource Inverter}}" />
If the first box is checked the property will be false, if the second one is checked it will be true, and if no checkbox is checked it will be null.
However, I agree with ANewGuyInTown that you'd be better off with an Enum, since the bool types are a bit confusing here (by the way, most of the converter can be re-used when working with a three-state enum instead of nullable boolean).

Make a "NotConverter" on one of the checkboxes. Here's my implementation I've been using for a while in Windows Store and Phone apps. WPF is similar.
/// <summary>
/// Converts a bool to it's oppisite and back.
/// </summary>
public sealed class NotConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (!(value is bool)) || !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return (value is bool) && (bool)value;
}
}
Register your converter in your App.xaml (or your view) file:
<Application.Resources>
<ResourceDictionary>
<converters:NotConverter x:Key="NotConverter"/>
</ResourceDictionary>
</Application.Resources>
Bind it in your view:
<CheckBox IsChecked="{Binding OnlyMusicWithErrorsChecked,Converter={StaticResource NotConverter}}"></CheckBox>
You could also name your other checkbox and bind to it's property like this:
<CheckBox x:Name="MyCheckBox" IsChecked="{Binding OnlyMusicWithErrorsChecked}"/>
<CheckBox IsChecked="{Binding ElementName=MyCheckBox,Path=IsChecked,Converter={StaticResource NotConverter}}"/>

Three state checkbox.
public bool CheckBox1
{
get { return _checkBox1; }
set
{
_checkBox1 = value;
if (value == true)
{
CheckBox2 = false;
}
OnPropertyChanged("CheckBox1");
}
}
private bool _checkBox2 = false;
public bool CheckBox2
{
get { return _checkBox2; }
set
{
_checkBox2 = value;
if (value == true)
{
CheckBox1 = false;
}
OnPropertyChanged("CheckBox2");
}
}
In Xaml Code something like this
<CheckBox Content="CheckBox1" IsChecked="{Binding CheckBox1, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Height="30" Width="100" />
<CheckBox Content="CheckBox2" IsChecked="{Binding CheckBox2, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Height="30" Width="100" />

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.

How to update radio button in view from viewmodel?

Say I have a few radio buttons grouped together in my view.xaml:
<RadioButton GroupName="Group" Content="Item1" Command="{Binding ChangeRadioSelectionCommand}" CommandParameter="Item1" />
<RadioButton GroupName="Group" Content="Item2" Command="{Binding ChangeRadioSelectionCommand}" CommandParameter="Item2" />
<RadioButton GroupName="Group" Content="Item3" Command="{Binding ChangeRadioSelectionCommand}" CommandParameter="Item3" />
Then in my viewmodel.cs I have something like:
public class ViewModel : BindableBase
{
private string radioSelection = "Item1";
public string RadioSelection
{
get { return this.radioSelection; }
set { SetProperty(ref this.radioSelection, value); }
}
public ViewModel()
{
this.ChangeRadioSelectionCommand = new DelegateCommand<string>(this.OnChangeRadioSelection, this.CanChangeRadioSelection);
}
public ICommand ChangeRadioSelectionCommand { get; private set; }
private void OnChangeRadioSelection(string radioSelection)
{
RadioSelection = radioSelection;
}
private bool CanChangeRadioSelection(string radioSelection) { return true; }
}
This works fine for getting values from the view into the viewmodel, but how would I go from the viewmodel to the view if something changes in the viewmodel. For simplicity, let's say I add a button to the xaml:
<Button Command="{Binding ResetRadioSelectionCommand}" />
All it would do is reset the radio selection to the first item and so the viewmodel.cs would look something like:
public class ViewModel : BindableBase
{
private string radioSelection = "Item1";
public string RadioSelection
{
get { return this.radioSelection; }
set { SetProperty(ref this.radioSelection, value); }
}
public ViewModel()
{
this.ChangeRadioSelectionCommand = new DelegateCommand<string>(this.OnChangeRadioSelection, this.CanChangeRadioSelection);
this.ResetRadioSelectionCommand = new DelegateCommand(this.OnResetRadioSelection, this.CanResetRadioSelection);
}
public ICommand ChangeRadioSelectionCommand { get; private set; }
private void OnChangeRadioSelection(string radioSelection)
{
RadioSelection = radioSelection;
}
private bool CanChangeRadioSelection(string radioSelection) { return true; }
public ICommand ResetRadioSelectionCommand { get; private set; }
private void OnResetRadioSelection()
{
RadioSelection = "Item1";
}
private bool CanResetRadioSelection() { return true; }
}
This would change radioSelection, but it won't reflect in the gui. Is there a way to do this? Or perhaps just a better way to deal with radio buttons in general?
It is completely the wrong way. Your ViewModel should contain a sensible property with sensible name. For example, CurrentMode.
FIRST SOLUTION
ViewModel
public enum DisplayMode { Vertical, Horizontal, Diagonal }
private DisplayMode currentMode;
public DisplayMode CurrentMode
{
get { return currentMode; }
set { SetProperty(ref currentMode, value); }
}
And now you can bind this property to RadioButton.IsChecked via IValueConverter:
<RadioButton GroupName="Group" Content="Vertical" IsChecked="{Binding CurrentMode, Converter={StaticResource enumToBoolConverter}, ConverterParameter=Vertical}" />
<RadioButton GroupName="Group" Content="Horizontal" IsChecked="{Binding CurrentMode, Converter={StaticResource enumToBoolConverter}, ConverterParameter=Horizontal}" />
<RadioButton GroupName="Group" Content="Diagonal" IsChecked="{Binding CurrentMode, Converter={StaticResource enumToBoolConverter}, ConverterParameter=Diagonal}" />
Converter is generic for all enums. You need to add it to your project and declare in resource-block of your view.
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
}
It's one of many solutions. You may not want to us enum for your property because the subject area is not mapped to enumeration of parameters. Then you can bind to text value:
SECOND SOLUTION
ViewModel
private string currentMode;
public string CurrentMode
{
get { return currentMode; }
set { SetProperty(ref currentMode, value); }
}
View
<RadioButton Name="RadioButton1"
GroupName="Group"
Content="Vertical"
IsChecked="{Binding Path=CurrentMode, Converter={StaticResource boolToStringValueConverter}, ConverterParameter=Vertical}" />
<RadioButton Name="RadioButton2"
GroupName="Group"
Content="Horizontal"
IsChecked="{Binding Path=CurrentMode, Converter={StaticResource boolToStringValueConverter}, ConverterParameter=Horizontal}" />
<RadioButton Name="RadioButton3"
GroupName="Group"
Content="Diagonal"
IsChecked="{Binding Path=CurrentMode, Converter={StaticResource boolToStringValueConverter}, ConverterParameter=Diagonal}" />
Converter
public class BooleanToStringValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (System.Convert.ToString(value).Equals(System.Convert.ToString(parameter)))
{
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (System.Convert.ToBoolean(value))
{
return parameter;
}
return null;
}
}
The general principle is to store meaningful projection of the subject area in ViewModels. There is no many sense if you'll keep store copy of view properties in your ViewModel. RadioSelection is a senseless name and it can't be correlated to model without additional commentaries.

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.

DataTemplate and WrapPanel visibility

I'm getting this error:
Provide value on 'System.Windows.Markup.StaticResourceHolder' threw an
exception.
With xaml code:
<WrapPanel Orientation="Horizontal" Grid.Row="0" >
<WrapPanel.Visibility>
<Binding Path="setVisible" Converter="{StaticResource BooleanToVisibilityConverter}" ConverterParameter="{Binding setVisible}"/>
</WrapPanel.Visibility>
//textblocks goes here
</WrapPanel>
and class:
public class dataTemplate_xItem
{
(...)
public bool setVisible { get; set; }
public sealed class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var flag = false;
if (value is bool)
{
flag = (bool)value;
}
else if (value is bool?)
{
var nullable = (bool?)value;
flag = nullable.GetValueOrDefault();
}
if (parameter != null)
{
if (bool.Parse((string)parameter))
{
flag = !flag;
}
}
if (flag)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var back = ((value is Visibility) && (((Visibility)value) == Visibility.Visible));
if (parameter != null)
{
if ((bool)parameter)
{
back = !back;
}
}
return back;
}
}
}
And before i'm adding item to ListView, checking
if(myValue != 0)
newItem.setVisible = true;
else
newItem.setVisible = false;
Any idea what goes wrong? :)
icebat is correct. The ConverterParameter is not a DependencyProperty and therefore, cannot be bound to. Looking at your xaml, you do not need the ConverterParameter. Nor do you need the extended markup for the binding expression. You xaml can simply be
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="Boolean2Visibility" />
</UserControl.Resources>
<WrapPanel Orientation="Horizontal" Grid.Row="0" Visibility="{Binding Path=setVisible, Converter={StaticResource Boolean2Visibility}}" />
This code assumes you are in a UserControl

WPF Visibility Binding to Converted Instance Property

So I have a button. I want to set the visibility of the button according to the value of an integer property of a class. This requires a data binding and a converter.
The XAML code for the button is as follows:
<Window.Resources>
<local:Button1VisibilityConverter x:Key="Button1VisibilityConverter"/>
<local:ModeValues x:Key="ModeHolder"/>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Left" Height="150" Margin="92,90,0,0" VerticalAlignment="Top" Width="301">
<Button Content="1" Height="58" Background="#FFA20000" Foreground="White" Visibility="{Binding Source={StaticResource ModeHolder}, Path=State, Converter=Button1VisibilityConverter}"/>
<Button Content="2" Height="58" Background="#FF16A200" Foreground="White"/>
<Button Content="3" Height="58" Background="#FF4200A2" Foreground="White"/>
</StackPanel>
</Grid>
My converter is as follows:
class Button1VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targettype, object parameter, System.Globalization.CultureInfo culture)
{
int mode = (int)value;
if (mode == ModeValues.Red)
return System.Windows.Visibility.Visible;
else
return System.Windows.Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
The class that has the property I want to control the visibility is as follows:
public class ModeValues : IObservable<int>
{
private int _state = -1;
public static int Red
{
get
{
return 0;
}
}
public static int Green
{
get
{
return 1;
}
}
public static int Purple
{
get
{
return 2;
}
}
public int State
{
get
{
return this._state;
}
set
{
this.State = value;
}
}
}
I have no idea why it isn't working. I thought I had to bind the visibility to the property of the instance of the ModeHolder, make the ModeHolder observable, and convert the int to a visibility. What am I missing?
Converter=Button1VisibilityConverter
should be:
Converter={StaticResource Button1VisibilityConverter}

Categories

Resources