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.
Related
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#
I've already made a workaround for this problem because of time constraints at work, although I still want to ask for learning purposes.
So I had this issue where I was making an editor screen for some record data, and in this record was a field called 'Quantity'. However, when designed, it was made a quantity placeholder, but it meant different things. So to explain, it is a SkuReference table, that has a 'Type' that defines if it's a 'Quantity per Pack', 'Roll Length', or 'CBC'. Well, for 'Quantity per Pack' and 'Roll Length', a simple number works, however for the 'CBC' (meaning, Corners/Borders/Centers) the data is stored as a JSON string object:
{ 'Corners': 10, 'Borders': 20, 'Centers': 30 }
Now on the WPF screen, if the data is identified as a 'CBC', I route the data to three textboxes, all bound to the 'Quantity' property of the parent object and using a converter and parameters to identify each one and I put the appropriate value into each textbox. Works fine.
The problem I have is when trying to work the ConvertBack part of the converter. I realized that I do not have reference to the original string property that I can edit and supply the new value to, or access to the other textboxes to just rebuild a new string to return. I was trying to come up with a resolution maybe using MultiBinding in my head, but could not completely come through with an answer.
Is this even possible? BTW I ended up just creating new properties that were split up and when the parent object was set parsed and passed around data. However, for future reference it would seem cleaner to me to just use the original data and a converter without the extra work.
Below is other code for reference:
XAML, UpsertSkuReference.Quantity is the JSON string above
<StackPanel Grid.Column="5">
<TextBlock Text="CBC" />
<StackPanel Orientation="Horizontal">
<TextBox Width="30" Text="{Binding UpsertSkuReference.Quantity, ConverterParameter=co, Converter={StaticResource CBCToIndividualConverter}}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Width="30" Margin="5,0,0,0" Text="{Binding UpsertSkuReference.Quantity, ConverterParameter=b, Converter={StaticResource CBCToIndividualConverter}}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Width="30" Margin="5,0,0,0" Text="{Binding UpsertSkuReference.Quantity, ConverterParameter=ce, Converter={StaticResource CBCToIndividualConverter}}" IsEnabled="{Binding CBCIsChecked}" />
</StackPanel>
</StackPanel>
Converter
public class CBCToIndividualConverter : IValueConverter
{
JavaScriptSerializer json = new JavaScriptSerializer();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//--value = CBC JSON object string
//--parameter = [co]: Corners, [b]: Borders, [ce]: Centers
if (value != null)
{
if (parameter == null) { throw new Exception("CBCToIndividualConverter: parameter cannot be null"); }
if (new string[] { "co", "b", "ce" }.Contains(parameter.ToString().ToLower()) == false)
{ throw new Exception("CBCToIndividualConverter: parameter must be 'co' for Corners, 'b' for Borders, or 'ce' for Centers"); }
CornerBorderCenterModel cbc = json.Deserialize<CornerBorderCenterModel>(value.ToString());
switch (parameter.ToString().ToLower())
{
case "co": { return cbc.Corners; }
case "b": { return cbc.Borders; }
case "ce": { return cbc.Centers; }
default: { return null; }
}
}
else { return null; }
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
//--value = number for parameter type
//--parameter = [co]: Corners, [b]: Borders, [ce]: Centers
//--?? Uh Oh
}
return null;
}
}
Converters are not supposed to be used like that. There are cleaner ways. I would suggest you a slight bigger refactoring:
Create 3 properties: Borders, Corners and Centers, on the class that contains the string Quantity (SkuReference?);
When you set any of them, update the Quantity; when you update the Quantity, you try to parse in a CornerBorderCenterModel instance and then update the 3 properties with the values of this instance. All this work is doing implementing the OnPropertyChanged method (see my code later);
In the view, bind every TextBox just with the relative property. This way, you need no converter at all (this works if the external DataContext is the instance of UpsertSkuReference; otherwise, you have to set the DataContext of the StackPanel this way: DataContext="{Binding UpsertSkuReference}", so the bindings of the 3 TextBoxes can find the properties on the object.
The whole round:
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace XXX
{
public class CornerBorderCenterModel
{
public int Corners { get; set; }
public int Borders { get; set; }
public int Centers { get; set; }
}
public class SkuReference : INotifyPropertyChanged
{
static JavaScriptSerializer json = new JavaScriptSerializer();
private static string[] _jsonStringParts =
new[] { nameof(Borders), nameof(Corners), nameof(Centers) };
public SkuReference()
{
PropertyChanged += OnPropertyChanged;
}
public int Corners
{
get { return _Corners; }
set
{
if (_Corners != value)
{
_Corners = value;
RaisePropertyChanged();
}
}
}
private int _Corners;
public int Borders
{
get { return _Borders; }
set
{
if (_Borders != value)
{
_Borders = value;
RaisePropertyChanged();
}
}
}
private int _Borders;
public int Centers
{
get { return _Centers; }
set
{
if (_Centers != value)
{
_Centers = value;
RaisePropertyChanged();
}
}
}
private int _Centers;
private void UpdateCBCFromQuantity()
{
//if Quantity is a CBC and is not null do the following:
var cbc = json.Deserialize<CornerBorderCenterModel>(_Quantity.ToString());
if (cbc != null)
{
Corners = cbc.Corners;
Borders = cbc.Borders;
Centers = cbc.Centers;
}
}
public string Quantity
{
get { return _Quantity; }
set
{
if (_Quantity != value)
{
_Quantity = value;
RaisePropertyChanged();
}
}
}
private string _Quantity;
private void UpdateJsonStringFromCBC()
{
Quantity = string.Format(
"{{ 'Corners': {0}, 'Borders': {1}, 'Centers': {2} }}",
_Corners,
_Borders,
_Centers);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_jsonStringParts.Contains(e.PropertyName))
UpdateJsonStringFromCBC();
else if (e.PropertyName == nameof(Quantity))
UpdateCBCFromQuantity();
}
}
}
<StackPanel Orientation="Horizontal" DataContext="{Binding UpsertSkuReference}">
<TextBox Text="{Binding Corners}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Text="{Binding Borders}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Text="{Binding Centers}" IsEnabled="{Binding CBCIsChecked}" />
</StackPanel>
Note that Quantity, Borders, Corners and Centers must raise notifications when they changed (just as I made, or using more advanced tools like the Reactive library), otherwise the whole round can't work.
Background Explanation
Okay so I'm currently binding a ContextMenu ItemsSource to an ObservableCollection of lets say TypeA
The following code is within the singleton class DataStore
private ObservableCollection<TypeA> TypeACollection = new ObservableCollection<TypeA>();
public ObservableCollection<TypeA> GetTypeACollection
{
get { return TypeACollection; }
}
public ObservableCollection<TypeA> GetFirstFiveTypeACollection
{
get
{
return TypeACollection.Take(5);
}
}
Now I've already successfully bound an ItemsControl to GetTypeACollection with the following XAML code:
The following code is within the MainWindow.xaml
<ItemsControl x:Name="TypeAList" ItemsSource="{Binding GetTypeACollection, Source={StaticResource DataStore}, UpdateSourceTrigger=PropertyChanged}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<User_Controls:TypeAUserControl Type="{Binding }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This works as expected, showing the TypeAUserControl, correctly formatted as intended with the data from TypeA
Now when i try to repeat this on a ContextMenu MenuItem by binding the ItemsSource to GetFirstFiveTypeACollection i initially see the expected results however upon deleting a TypeA object the MainWindow ItemsControl is updated where the ContextMenu is not.
I believe this is due to the fact that the binding itself is between the ContextMenu and a 'new' ObservableCollection<TypeA> (as seen in GetFirstFiveTypeAColletion ).
I have also attempted this through use of an IValueConverter
The following code is within the class ValueConverters
public class ObservableCollectionTypeAResizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<TypeA> TypeACollection = value as ObservableCollection<TypeA>;
return TypeACollection.Take(System.Convert.ToInt32(parameter));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
The XAML code I have tried.
Using IValueConverter
<MenuItem Header="First 5 Type A" Name="MI_FirstFiveTypeA"
ItemsSource="{Binding DATA_STORE.GetTypeACollection, ConverterParameter=5,
Converter={StaticResource ObservableCollectionTypeAResizeConverter},
Source={StaticResource DataStore}}"
/>
Using GetFirstFiveTypeACollection
<MenuItem Header="First 5 Type A" Name="MI_FirstFiveTypeA"
ItemsSource="{Binding DATA_STORE.RecentTimers, Source={StaticResource DataStore},
UpdateSourceTrigger=PropertyChanged}"
/>
I've no idea what to try and do next or how i should be doing this, Any help would be greatly appreciated!
Edit
Okay so I have changed the following
DataStore.cs
private ObservableCollection<TimerType> TimerTypes = new ObservableCollection<TimerType>();
public ObservableCollection<TimerType> getTimerTypes
{
get
{
return new ObservableCollection<TimerType>(TimerTypes.OrderByDescending(t => t.LastUsed));
}
}
public ObservableCollection<TimerType> RecentTimers
{
get
{
return new ObservableCollection<TimerType>(TimerTypes.OrderByDescending(t => t.LastUsed).Take(5));
}
}
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
//THIS IS IN THE ADD METHOD
TimerTypes.Add(timer);
NotifyPropertyChanged("getTimerTypes");
NotifyPropertyChanged("RecentTimers");
NotifyPropertyChanged("TimerTypes");
//THIS IS IN THE REMOVE METHOD
TimerTypes.Remove(Timer);
NotifyPropertyChanged("getTimerTypes");
NotifyPropertyChanged("RecentTimers");
NotifyPropertyChanged("TimerTypes");
MainWindow.xaml
<ItemsControl x:Name="TimersList" ItemsSource="{Binding Path=getTimerTypes, UpdateSourceTrigger=PropertyChanged}" >
MainWindow.xaml.cs
//Lists are populated in DataStore.cs before this.
DataContext = DataStore.DATA_STORE;
InitializeComponent();
NotifyIcon.xaml
<MenuItem Header="Recent Timers" Name="MIRecent" ItemsSource="{Binding RecentTimers, UpdateSourceTrigger=PropertyChanged}"/>
NotifyIcon.xaml.cs
DataContext = DataStore.DATA_STORE;
InitializeComponent();
So everything binds correctly at start but when a TimerType is deleted the PropertyChangedEventHandler is always NULL. I thought this was a DataContext issue but I'm pretty sure I have the DataContext and all of the Bindings correct now?
Singleton Instance Creation
private static readonly DataStore Instance = new DataStore();
private DataStore() { }
public static DataStore DATA_STORE
{
get { return Instance; }
set { }
}
Unfortunately, by using Take (or any of the LINQ methods) you get a straight IEnumerable back. When you have bound to it, there is no INotifyCollectionChanged so changes to the collection will not cause the UI to update.
There's no real workaround due to the lack of INotifyCollectionChanged. Your best bet is to raise PropertyChanged against your "subset" property when you add/remove items to force the UI to renumerate the IEnumerable.
You might be able to solve this using a CollectionView
public class MyViewModel
{
CollectionView _mostRecentDocuments;
public MyViewModel()
{
Documents = new ObservableCollection<Document>();
_mostRecentDocuments = new CollectionView(Documents);
_mostRecentDocuments .Filter = x => Documents.Take(5).Contains(x);
}
public ObservableCollection<Document> Documents { get; private set; }
public CollectionView MostRecentDocuments
{
get
{
return _mostRecentDocuments;
}
}
}
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'm new to programming in Windows Phone 8. I am studying "The Binding". I try to bind the property "Value" of a Slider, but when running the application I do not see any change.
The XAML code is this:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<Slider Minimum="1" Maximum="100" Value="{Binding Valor}" />
</StackPanel>
</Grid>
The ViewModelBase class is this:
public class VMBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public VMBase() {}
public void RaisePropertyChanged(string PropertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
The ViewModel class is this:
public class VMSlider : VMBase {
private int _valor;
public VMSlider() {
_valor = 43;
}
public int Valor {
get { return _valor; }
set {
_valor = value;
RaisePropertyChanged("Valor");
}
}
}
In the code-behind class of my XAML y write this:
this.DataContext = new ViewModel.VMSlider();
I need to say why.
Thank you.
The Value property on the Slider control is of type Double. Your Binding doesn't work because Valor is a value of the wrong type.
You must either implement a Value Converter or change Valor to a Double.