so I have ComboBox with some items and its SelectedIndex is bound TwoWay with a property of type MyTypeEnum the idea is its selected index value can be set by an enum to int converter and when user changes selection on combobox itself then the new selectedIndex should update the value it is bound to. it is working fine OneWay i.e: from property to SelectedIndex, but not working reverse so with breakpoints I have confirmed that Set method of my bound property does not execute when I change selection of combobox however the ConvertBack method of my converter does execute which is like it should.
I have prepared a minimal and simple code repo to reproduce the issue : https://github.com/touseefbsb/ComboBoxToEnumBug
Code
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<local:IdToIndexConverter x:Key="IdToIndexConverter"/>
</Page.Resources>
<Grid x:DefaultBindMode="TwoWay">
<ComboBox SelectedIndex="{x:Bind ViewModel.MyTypeEnum, Converter={StaticResource IdToIndexConverter}}" >
<ComboBoxItem>item 1</ComboBoxItem>
<ComboBoxItem>item 2</ComboBoxItem>
<ComboBoxItem>item 3</ComboBoxItem>
</ComboBox>
</Grid>
MainViewModel
public class MainViewModel : Observable
{
private MyTypeEnum _myTypeEnum = MyTypeEnum.Type1;
public MyTypeEnum MyTypeEnum
{
get => _myTypeEnum;
set => Set(ref _myTypeEnum, value);
}
}
Observable
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
MyTypeEnum
//using byte as parent so that I can start count from 1 instead of 0
public enum MyTypeEnum : byte
{
Type1 = 1,
Type2,
Type3
}
Converter
public class IdToIndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language) => System.Convert.ToInt32(value) - 1;
public object ConvertBack(object value, Type targetType, object parameter, string language) => ((int)value) + 1;
}
I have confirmed that Set method of my bound property does not execute when I change selection of combobox however the ConvertBack method of my converter does execute which is like it should.
Your ConvertBack method returns an int but it should return an MyTypeEnum.
The source property of the view model cannot be set to an int, only to a MyTypeEnum.
Cast int to enum in C#
Related
I have a standard Enum that will either be Yes or No:
public enum YesOrNo
{
Yes,
No
}
My base Model Class has a YesOrNo property like this:
public class Group : NotifyPropertyChanged
{
private YesOrNo groupOperator;
public YesOrNo GroupOperator
{
get
{
return this.groupOperator;
}
set
{
this.groupOperator = value;
OnPropertyChanged("GroupOperator");
}
}
In my View, I am using a ToggleSwitch, similar to a slider you would see on a Mobile phone. Sliding back and forth should effectively reassign the value of the Enum. So it will default as Yes and sliding the toggle will set the Enum value to No and alternatively.
If I were to have a test method that reassigns the Enum when the Checked command is hit, the PropertyChanged event is fired so I know that is technically working. I am just wondering how I could go about alternating values in the Enum.
This is the ToggleButton in my XAML:
<ToggleButton Style="{StaticResource ToggleViewSwitch}" Command="{Binding SetOperatorCommand, UpdateSourceTrigger=PropertyChanged}" IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
And this is my Main View Model, where I hold the Command and the test method to assign the value manually:
private bool isChecked = false;
public bool IsChecked
{
get
{
return this.isChecked;
}
set
{
this.isChecked = value;
OnPropertyChanged("IsChecked");
}
}
private RelayCommand setOperatorCommand;
public ICommand SetOperatorCommand
{
get
{
if (this.setOperatorCommand == null)
{
this.setOperatorCommand = new RelayCommand(
x => ToggleGroupOperator());
}
return this.setOperatorCommand;
}
}
private void ToggleGroupOperator()
{
if (IsChecked)
{
TopLevelGroup.GroupOperator = YesNo.No;
}
else
{
TopLevelGroup.GroupOperator = YesNo.Yes;
}
}
First make a Converter...
public class YesOrNoToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> (value is YesOrNo yesOrNo && yesOrNo == YesOrNo.Yes) ? true : false;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> (value is bool isYes && isYes) ? YesOrNo.Yes : YesOrNo.No;
}
Then reference the converter during binding...
<Window.Resources>
<Converters:YesOrNoToBooleanConverter x:Key="YesOrNoToBooleanConverter" />
</Window.Resources>
<Grid>
<CheckBox IsChecked="{Binding GroupOperator, Converter={StaticResource YesOrNoToBooleanConverter}}" />
</Grid>
This will allow your ViewModel to remain using the enum without any overhead and the view to bind without any overhead; leave this binding manipulation work to converters.
In the following DataTemplate, the first binding doesn't work while the 2nd one works, and I would like to know why.
<local:IsEnabledConverter x:Key="isEnabled"/>
<local:Boolean2TextConverter x:Key="txtConverter"/>
<DataTemplate x:Key="fileinfoTemplate" DataType="{x:Type local:MyFileInfo}">
<StackPanel>
<Label x:Name="1stLabel" Content="{Binding Path=Filename}" IsEnabled="{Binding Path=., Converter={StaticResource isEnabled}}"/> <--- doesn't work
<Label x:Name="2ndLabel" Content="{Binding Path=IfPrint, Converter={StaticResource txtConverter}}" IsEnabled="{Binding Path=IsChecked, ElementName=ckBox}"/> <--- works
<CheckBox x:Name="ckBox" IsChecked="{Binding Path=IfPrint}" IsEnabled="{Binding Path=IsValid}" Style="{StaticResource printCkBox}"/>
</StackPanel>
</DataTemplate>
IsEnabledConverter:
class IsEnabledConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
MyFileInfo f = value as MyFileInfo;
return f.IsValid && f.IfPrint;
}
//... omit ConvertBack NotImplementedException stuff
}
Boolean2TextConverter:
class IsEnabledConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
Boolean b = (Boolean)value;
return b.ToString();
}
//similarly omit ConvertBack here
}
Code for MyFileInfo:
public class MyFileInfo {
public string IfPrint {
get;
set;
}
public string IsValid {
get;
set;
}
...
}
Problem: When the CheckBox is toggled, the 2nd Label grays out and shows "false", or becomes normal and shows "true", as it should. However, the first Label doesn't change at all; its IsEnabled state is supposed be the conjunction of two Booleans, one of which is changed by the CheckBox. What is wrong? (note that the IsEnabledConverter is called once upon GUI initialization, but not called again when its binding source changes.)
There are 2 issues here. First you have to implement INotifyPropertyChanged for the ViewModel MyFileInfo. Secondly you have to use MultiBinding here. Because I don't think we have some way to trigger updating the target (such as when toggling the CheckBox) if you bind the whole view model to the IsEnabled target. So here is how it should be done:
Your view model:
public class MyFileInfo : INotifyPropertyChanged {
bool _ifPrint;
bool _isValid;
public bool IfPrint {
get { return _ifPrint; }
set {
if(_ifPrint != value) {
_ifPrint = value;
OnPropertyChanged("IfPrint");
}
}
}
public bool IsValid {
get { return _isValid; }
set {
if(_isValid != value) {
_isValid = value;
OnPropertyChanged("IsValid");
}
}
}
//Implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string prop){
var handler = PropertyChanged;
if(handler != null) handler(this, new PropertyChangedEventArgs(prop));
}
//.... should do the same for the remaining properties....
//...
}
Here is the converter used for MultiBinding, which should implement IMultiValueConverter (instead of IValueConverter):
class IsEnabledConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture) {
if(values.Length == 2){
return (bool) values[0] && (bool) values[1];
}
return false;
}
//... omit ConvertBack NotImplementedException stuff
}
Here is the modifed XAML (to use MultiBinding instead):
<Label x:Name="firstLabel" Content="{Binding Path=Filename}">
<Label.IsEnabled>
<MultiBinding Converter="{StaticResource isEnabled}">
<Binding Path="IsValid"/>
<Binding Path="IfPrint"/>
</MultiBinding>
</Label.IsEnabled>
</Label>
Now one of IsValid and IfPrint changing will trigger the MultiBinding's Converter. Here you can also bind to IsChecked of the CheckBox directly instead of indirectly via IfPrint.
PS: Note Name used in XAML (as well as in codebehind) must not start with number.
Since the instance of MyFileInfo does not change while you check/uncheck the checkbox hence IsEnabledConverteris not getting called.
In order to Enable/Disable your 1stLabel depending on two properties, either use MultiValueConverter or use MultiDataTrigger by applying Style to your Label.
I am trying to understand the concept of DataBinding a combobox with an object.
I have the following class:
public class employmentApplication
{
private byte appType = 0; // 1 = normal; 2 = expedited
public byte AppType
{
get { return appType ; }
set
{
appType = value;
this.OnPropertyChanged("AppType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
My xaml for the combobox is
<ComboBox>
<ComboBoxItem Content="Normal" />
<ComboBoxItem Content="Expedited" />
</ComboBox>
I am not sure where to begin to bind my combobox to the AppType since it has to convert it from a string ("Normal", "Expedited") to a byte (0, 1) and back between the object and the combobox.
Thanks for any help in advance!
You could do this several ways, however here is a simple solution todo what you have asked. Note, your DataContext will need to be set to your instance of the class you want to represent.
<ComboBox SelectedValue="{Binding AppType, Mode=TwoWay}"
SelectedValuePath="Tag">
<ComboBoxItem Content="Normal" Tag="0"/>
<ComboBoxItem Content="Expedited" Tag="1"/>
</ComboBox>
SelectedValue is binded to your property, and SelectedValuePath is the property of ComboBoxItem (in this case) that will represent the selected value.
This is a very simple solution, and you may need to alter it for your needs. Additionally, I would consider using an Enum to represent this data. It will make it a little easier to understand what you are trying todo in code.
Edit
Here is an example of using an enum with the above example. I'm going to use your already existing code as a base.
Create an enum (preferably in a new file).
public enum AppType
{
Normal,
Expedited
}
Now modify your property to use the enum
public AppType AppType
{
get
{
return appType;
}
set
{
if( Equals( appType, value ) ) return;
appType = value;
OnPropertyChanged( "AppType" );
}
}
Now you can use my above example, but with an enum:
<ComboBox SelectedValue="{Binding AppType, Mode=TwoWay}"
SelectedValuePath="Tag">
<ComboBoxItem Content="Normal" Tag="{x:Static local:AppType.Normal}"/>
<ComboBoxItem Content="Expedited" Tag="{x:Static local:AppType.Expedited"/>
</ComboBox>
The following is nice for having a little more control over what is displayed in the ComboBox. However you can also have it display every enum value, which is nice because if you add enum values in the future, they will automatically show up in the ComboBox. See this question and answer for a way of doing that.
My advice is to use ValueConverter. It's more flexable solution than using Tag values and it's looks nice because you are separating conversion logic in its own class. Also I noticed that in your data class you declared PropertyChanged event but not implemented INotifyPropertyChanged interface so you can't have two way binding in this case. Full example:
Your data class (with my fixes)
public class EmploymentApplication : INotifyPropertyChanged
{
private byte appType = 0; // 1 = normal; 2 = expedited
public byte AppType
{
get { return appType; }
set
{
appType = value;
OnPropertyChanged("AppType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Value converter
public class AppTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var b = (byte)value;
if (b == 1) return "Normal";
if (b == 2) return "Expedited";
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var strValue = (string) value;
byte result = 0;
if (strValue.Equals("Normal", StringComparison.Ordinal))
{
result = 1;
}
else if (strValue.Equals("Expedited", StringComparison.OrdinalIgnoreCase))
{
result = 2;
}
return result;
}
}
xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new EmploymentApplication();
}
}
Xaml
<Window x:Class="WpfConvertion.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfConvertion"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:AppTypeConverter x:Key="Converter"></local:AppTypeConverter>
</Window.Resources>
<Grid>
<ComboBox Height="20" SelectedValue="{Binding AppType, Converter={StaticResource Converter}}" SelectedValuePath="Content">
<ComboBoxItem>Normal</ComboBoxItem>
<ComboBoxItem>Expedited</ComboBoxItem>
</ComboBox>
</Grid>
Pay attention on this line: xmlns:local="clr-namespace:WpfConvertion". You must set your own namespace here instead of my WpfConvertion.
The simplest way is, if yout Normal value is 0 and your Expedited value is 1, use SelectedIndex to bind to.
<ComboBox SelectedIndex="{Binding AppType}" >
There are several ways to achieve this the simplest could be to add a bind to your AppType with SelectedIndex property of your ComboBox
Note that you should add INotifyPropertyChanged to let your binding work
and in code behind do the following
namespace WpfApplication8
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
employmentApplication emp = new employmentApplication();
public MainWindow()
{
InitializeComponent();
this.DataContext = emp;
}
}
public class employmentApplication:INotifyPropertyChanged
{
private byte appType = 0; // 1 = normal; 2 = expedited
public byte AppType
{
get { return appType; }
set
{
appType = value;
this.OnPropertyChanged("AppType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
}
You could use a converter, or tags, as the other answers have shown. But you could also define your item-source as a class rather than rely on hand-waving to link integers to strings.
So your item source items could be a class like this:
public class AppType
{
public string Name;
public byte Type;
}
You can then use SelectedValue and SelectedValuePath binding on your combobox to define what changes in your datacontext, and what property is used in the lookup list.
<ComboBox
ItemSource = {Binding ListOfAppTypes}
SelectedValue="{Binding Type, Mode=TwoWay}"
SelectedValuePath="Name">
</ComboBox>
You could also define a Template for your Combobox where you gain more control over how the lookup list of items is displayed.
I understand that the Visibility property of a control cannot be bound to data in the same way that other properties can. It needs some kind of converter(?). In trying to implement the solution from this question I run into a compiler error that says: The resource "BoolToVisible" could not be resolved. I'm guessing that I have to create a ResourceKey named BoolToVisible, I just don't know how.
I'm requesting that someone show me the right way to Bind to the Visibility property of a control.
*The control that I am adding this to is a radio button.
* I have a bool property for isVisible in my Data Model that will be bound to this radio button.
Data Model Property:
private bool _isVisible = true;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
NotifyPropertyChange(() => IsVisible);
}
}
XAML:
<RadioButton Visibility="{Binding DataModel.IsVisible,Converter={StaticResource ResourceKey=BoolToVisible},RelativeSource={RelativeSource TemplatedParent}}" ... />
Thank you.
2 examples :
The first using a Converter like stated in the question :
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || !(value is bool))
return Binding.DoNothing;
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
in xaml :
<Window x:Class="Stackoverflow.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"
xmlns:local="clr-namespace:Stackoverflow"
>
<Window.Resources>
<local:BooleanToVisibilityConverter x:Key="booleanToVisibiltyConverter"/>
</Window.Resources>
<Grid>
<Button Visibility="{Binding IsSomeThing,Converter={StaticResource booleanToVisibiltyConverter}}"/>
</Grid>
the second :
in your DataContext you can literly hold a Visibility Property
cs :
private Visibility _myControlVisibility;
public Visibility MyControlVisibility
{
get { return _myControlVisibility; }
set { _myControlVisibility = value; }
}
xaml :
<Button Visibility="{Binding MyControlVisibility}"/>
You can binding visibility with a property, you just need to have a Dependency Property of Visibility field as:
Public Property MyVisibility As Windows.Visibility
Get
Return GetValue(MyVisibilityProperty)
End Get
Set(ByVal value As Windows.Visibility)
SetValue(MyVisibilityProperty, value)
End Set
End Property
Public Shared ReadOnly MyVisibilityProperty As DependencyProperty = _
DependencyProperty.Register("MyVisibility", _
GetType(Windows.Visibility), GetType(MyWindow), _
New PropertyMetadata(Nothing))
Then do the binding as the usual (The code is in VB).
Remember that in the New PropertyMetadata you can set an initial state for the object eg:
Public Shared ReadOnly MyVisibilityProperty As DependencyProperty = _
DependencyProperty.Register("MyVisibility", _
GetType(Windows.Visibility), GetType(MyWindow), _
New PropertyMetadata(Windows.Visibility.Hidden))
I have a boolean property (that does called INotifyPropertyChanged in the setter) that is bound to a button.IsEnabled property in my XAML. Currently I'm using a TwoWay binding, but this is causing problems and I only need a OneWay binding. My problem is that the converter I'm using doesn't get called beyond the first time the program starts up. I've put breakpoints in the setter and it gets called loads, but the Convert() method doesn't get called at all. Why is this?
Some code:
public bool IsSaving
{
get
{
return _isSaving;
}
set
{
_isSaving = value;
NotifyOfPropertyChange(() => IsSaving);
}
}
and the XAML:
IsEnabled="{Binding Path=IsSaving, Mode=OneWay, Converter={StaticResource booleanToNotEnabledConverter}}"
The converter really just returns !(bool)value so the button gets disabled when IsSaving is true.
Some changes at runtime might cause the binding to break (since you bind to the DataContext + a relative path), if you use Visual Studio make sure to check the Output-window for any binding errors.
Edit: Since it has not been noted: That is a stardard binding and there is nothing wrong with the posted code, the problem has to be caused by the context.
Here is the code I used and this works:
Converter:
using System.Windows.Data;
using System;
namespace SilverlightApplication1
{
public class BooleanToNotEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML:
<UserControl x:Class="SilverlightApplication1.MainPage"
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:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<local:BooleanToNotEnabledConverter x:Key="booleanToNotEnabledConverter" />
</UserControl.Resources>
<StackPanel Margin="100">
<Button Content="Flip"
Click="Button_Click" />
<TextBlock Text="{Binding IsSaving}"
Height="20" />
<Button IsEnabled="{Binding IsSaving, Mode=OneWay, Converter={StaticResource booleanToNotEnabledConverter}}"
Content="Some Button" />
</StackPanel>
</UserControl>
Code behind:
using System.Windows.Controls;
using System.Windows;
using System.ComponentModel;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
private Data _data;
public MainPage()
{
InitializeComponent();
_data = new Data { IsSaving = true };
this.DataContext = _data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_data.IsSaving = !_data.IsSaving;
}
}
public class Data : INotifyPropertyChanged
{
#region IsSaving Property
private bool _isSaving;
public bool IsSaving
{
get
{
return _isSaving;
}
set
{
if (_isSaving != value)
{
_isSaving = value;
OnPropertyChanged("IsSaving");
}
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var p = PropertyChanged;
if (p != null)
{
p(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Are you sure you invoke the PropertyChanged event handler with the correct string?
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsSaving"));