WPF Converter JSON string to multiple textboxes - 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.

Related

Calculated field not updating until edited in UI

I'm trying to test out data binding with XAML and C# as a novice programmer. I have two sliders that are bound to properties and I want to update a TextBox with the sum of the two values of the properties set by the sliders.
I'm using INotifyPropertyChanged and tried changing every property I could find but I can't get the textbox to update until I edit the textbox, at which point, the textbox updates to the correct value. Using UpdateSourceTrigger=PropertyChanged only updates the textbox as soon as I edit the textbox instead of when I select another element. I've tried writing a separate event handler that doesn't use [CallerNameMember] and uses a specified property but it didn't seem to change anything.
<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<TextBox Grid.Row="0"
Text="{Binding BoundNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="20"
FontWeight="Bold"
AllowDrop="False" />
<Slider Grid.Row="1"
Value="{Binding BoundNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Maximum="100"
Minimum="10"
IsSnapToTickEnabled="True"
TickFrequency="10" />
<TextBox Grid.Row="2"
Text="{Binding BoundNumber2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AllowDrop="False" />
<Slider Grid.Row="3"
Value="{Binding BoundNumber2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Maximum="100"
Minimum="10"
IsSnapToTickEnabled="True"
TickFrequency="10" />
<TextBox Grid.Row="4"
Name="MathBox"
Text="{Binding QuickMath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}">
</TextBox>
</Grid>
public partial class OrderScreen : INotifyPropertyChanged
{
public OrderScreen()
{
DataContext = this;
InitializeComponent();
}
private int quickMath;
public int QuickMath
{
get { return _boundNumber + _boundNumber2; }
set
{
if (value != quickMath)
{
quickMath = value;
OnPropertyChanged();
}
}
}
private int _boundNumber;
public int BoundNumber
{
get { return _boundNumber; }
set
{
if (_boundNumber != value)
{
_boundNumber = value;
// MathBox.Text = quickMath.ToString();
OnPropertyChanged();
}
}
}
private int _boundNumber2;
public int BoundNumber2
{
get { return _boundNumber2; }
set
{
if (_boundNumber2 != value)
{
_boundNumber2 = value;
MathBox.Text = quickMath.ToString();
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I can get it to work with the commented out MathBox.Text = quickMath.ToString(); but I was hoping there was a better way to do this with data binding. Thanks in anticipation!
Binding mechanism subscribes to the PropertyChanged event of DataSource object, so there is no need to "initialize" the event along with the INPC implementation, but as you might have noticed, PropertyChanged event for the QuickMath property is indeed never triggered when BoundNumber or BoundNumber2 are changed.
You can fix it in different ways, e.g. explicitly call OnPropertyChanged for all affected properties:
private int _boundNumber;
public int BoundNumber
{
get { return _boundNumber; }
set
{
if (_boundNumber != value)
{
_boundNumber = value;
OnPropertyChanged();
OnPropertyChanged(nameof(QuickMath));
}
}
}
Note that this way you can keep QuickMath property a read-only. This approach works nicely in other situations, like with time-related properties, say if your data source property formats a string like "Edited 2 minutes ago" based on a recorded timestamp and current time and you call PropertyChanged as a timed task.
public int QuickMath => _boundNumber + _boundNumber2;
Alternatively, you can update QuickMath along with modifying BoundNumber and BoundNumber2 to trigger OnPropertyChanged() call inside QuickMath setter:
private int _boundNumber2;
public int BoundNumber2
{
get { return _boundNumber2; }
set
{
if (_boundNumber2 != value)
{
_boundNumber2 = value;
OnPropertyChanged();
QuickMath = BoundNumber + BoundNumber2;
}
}
}
This makes sense if the logic in QuickMath wouldn't allow making it a read-only property. In this case you have to adjust the getter accordingly and use private or protected setter there to avoid data inconsistency and unexpected behavior.
private int _quickMath;
public int QuickMath
{
get { return _quickMath; }
private set
{
if (value != _quickMath)
{
_quickMath = value;
OnPropertyChanged();
}
}
}
In both cases there is no need for two-way binding to QuickMath:
<TextBlock Grid.Row="4" Text="{Binding QuickMath, Mode=OneWay}"/>
On a side-note and looking at the rest of the code, it really worth mentioning that binding mechanism is expected to segregate UI from the data, where XAML knows about data source object properties (names and types) but not about it's internal implementation, while data source object can have no knowledge about XAML at all. So
there should be no calls from data object to FrameworkElements like MathBox.Text
it's considered a good design to have data object class completely separate from the page or control class.
Hope this helps.
You haven't initialized your PropertyChanged event anywhere, so it will never be called. Declare and initialize it like so:
public event PropertyChangedEventHandler PropertyChanged = delegate { };
A TextBox bound to the calculated property QuickMath should receive PropertyChanged event from it in order to update the text in the field.
Despite your OrderScreen implementing the INotifyPropertyChanged interface, it will not raise the event when QuickMath is changed because its setter (where the raising of the event is located) is never called. You can fix it, for example, by calling the QuickMath setter from the independent properties setters as suggested in other answers or delegate that work to DependenciesTracking lib:
public class OrderScreen : INotifyPropertyChanged
{
private readonly IDependenciesMap<OrderScreen> _dependenciesMap =
new DependenciesMap<OrderScreen>()
.AddDependency(i => i.QuickMath, i => i.BoundNumber + i.BoundNumber2, i => i.BoundNumber, i => i.BoundNumber2);
public OrderScreen() => _dependenciesMap.StartTracking(this);
private int _boundNumber2;
private int _boundNumber;
private int _quickMath;
public int QuickMath
{
get => _quickMath;
private set
{
if (value != _quickMath)
{
_quickMath = value;
OnPropertyChanged();
}
}
}
public int BoundNumber
{
get => _boundNumber;
set
{
if (_boundNumber != value)
{
_boundNumber = value;
OnPropertyChanged();
}
}
}
public int BoundNumber2
{
get => _boundNumber2;
set
{
if (_boundNumber2 != value)
{
_boundNumber2 = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class Tests_SO_56623403
{
[Test]
public void Test_SO_56623403()
{
var sut = new OrderScreen();
var raisedEventsCount = 0;
sut.PropertyChanged += (_, args) =>
{
if (args.PropertyName == nameof(OrderScreen.QuickMath))
++raisedEventsCount;
};
Assert.Multiple(() =>
{
Assert.That(sut.QuickMath, Is.EqualTo(0));
Assert.That(raisedEventsCount, Is.EqualTo(0));
});
sut.BoundNumber = 12;
Assert.Multiple(() =>
{
Assert.That(sut.QuickMath, Is.EqualTo(12));
Assert.That(raisedEventsCount, Is.EqualTo(1));
});
sut.BoundNumber2 = 40;
Assert.Multiple(() =>
{
Assert.That(sut.QuickMath, Is.EqualTo(52));
Assert.That(raisedEventsCount, Is.EqualTo(2));
});
}
}

WPF DataGrid, customizing the display with a converter

I have a unique situation. In one class, I have an inner class that acts as pretty much just a "display" class. In the outer class there is a method called GetDisplayObject that returns a type of the inner class.
I'm trying to bind to a datagrid with the outer class, but by using a converter I'd like to get the correct display. This way I won't have to change a bunch of code in our application and just add a few lines in a couple .xaml files.
I made a little test app that pretty much sums up my problem at it's most basic level. Ideally I'd like to solve the problem by using a converter and returning values only as a display, that way when i'm using the SelectedItem I won't have to change a ton of code that is depending on that certain type(In this case would be the DataObject type).
So here is the objects i'm stuck dealing with
namespace TestApp
{
public class DataObject
{
public class DataObjectDisplay
{
public string ObjectDisplay { get; set; }
}
// props
public int Id { get; set; }
public object Object1 { get; set; }
public object Object2 { get; set; }
// method for getting the display
public DataObjectDisplay GetDisplayObject()
{
DataObjectDisplay display = new DataObjectDisplay();
// logic for determining which object should be displayed
if(Object1 == null)
{
display.ObjectDisplay = "Object1";
}
else
{
display.ObjectDisplay = "Object2";
}
return display;
}
}
}
Here is the Code Behind of my xaml
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace TestApp
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
this.DataObjectCollection = new ObservableCollection<DataObject>();
this.DataObjectCollection.Add(new DataObject() { Id = 1, Object1 = "this", Object2 = "that" });
this.DataObjectCollection.Add(new DataObject() { Id = 1, Object2 = "that" });
this.SelectedItem = new DataObject();
}
private ObservableCollection<DataObject> dataObjectCollection;
private DataObject selectedItem;
public ObservableCollection<DataObject> DataObjectCollection
{
get { return this.dataObjectCollection; }
set
{
dataObjectCollection = value;
OnNotifyPropertyChanged("DataObjectCollection");
}
}
public DataObject SelectedItem
{
get { return this.selectedItem; }
set
{
selectedItem = value;
OnNotifyPropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotifyPropertyChanged(string property = "")
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Here is the xaml(This is something like what i'd want to do, using the itemtemplate or something similar, and then a converter to call this GetDisplay function)
<Window x:Class="TestApp.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:TestApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:DataObjectToDisplayDataObjectConverter x:Key="ToDisplayConverter"/>
</Window.Resources>
<StackPanel>
<DataGrid ItemsSource="{Binding DataObjectCollection}" SelectedItem="{Binding SelectedItem}" MinHeight="200">
<DataGrid.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Converter={StaticResource ToDisplayConverter}}"/>
</DataTemplate>
</DataGrid.ItemTemplate>
</DataGrid>
</StackPanel>
</Window>
And Finally the converter
using System;
using System.Globalization;
using System.Windows.Data;
namespace TestApp
{
public class DataObjectToDisplayDataObjectConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == typeof(DataObject))
{
DataObject dataObj = (DataObject)value;
dataObj.GetDisplayObject();
return dataObj;
}
return "Invalid Value";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I am open to suggestions, but this is a small example. In our actual application changing one thing will most likely cascade into a huge ordeal.
If I were you I would have change close coupling of view with ViewModel, Like creating new MainWindowViewModel class & having all properties in it.
Another thing I see GetDisplayObject method, what's the need of calling such a method from converter.
You can re-factor this code & put in the converter something like this.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DataObject dataObj = value as DataObject;
if (value == null)
{
return "Invalid Value";
}
if (dataObj.Object1 == null)
{
return "Object1";
}
return "Object2";
}
Luckily since it was a matter of if one object was null and it's related strings were empty, I was able to use Priority Binding with a converter to return DependencyProperty.UnsetValue and force it to use the next binding. I think this was the best solution for the situation.
So the xaml ended up looking like this.
<DataGrid ItemsSource="{Binding DataObjectCollection}" SelectedItem="{Binding SelectedItem}" MinHeight="200">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<PriorityBinding>
<Binding Path="Object1" Converter="{StaticResource EmptyStringToDependencyPropertyUnset}"/>
<Binding Path="Object2"/>
</PriorityBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And the Converter ended up like this
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == null || value as string == string.Empty)
{
return DependencyProperty.UnsetValue;
}
return value;
}

Bind object to ConverterParameter fails with XamlParseException in WP8.1 Silverlight

Initial situation
I have a Windows Phone 8.1 Silverlight app where I have a model that contains several properties as shown below (just an excerpt of 3 properties, it has a lot more).
public class WorkUnit : INotifyPropertyChanged
{
public DateTime? To
{
get { return Get<DateTime?>(); }
set
{
Set(value);
OnPropertyChanged("To");
OnPropertyChanged("ToAsShortTimeString");
}
}
public string ToAsShortTimeString
{
get
{
if (To.HasValue)
{
if (Type == WorkUnitType.StartEnd)
return To.Value.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
var duration = To.Value - From;
return DateHelper.FormatTime(duration, false);
}
return null;
}
}
public short? Type
{
get { return Get<short?>(); }
set
{
Set(value);
OnPropertyChanged("Type");
}
}
}
I'm using MVVMLight. There are several work units in an ObservableCollection that is bound to a list box on a Windows Phone page. The collection itself is part of a (WorkDay) view model which in turn is bound to the page itself.
What I want to do
I have a lot of properties in my model that are just used to format some properties for the UI. One such is ToAsShortTimeString which returns the time given by the To property, depending on the Type and the From properties, formatted as string.
In order to clean up my model I want to remove such formatter-properties as much as possible and use converters (IValueConverter) as much as possible. One further reason to move away from such properties is that the database that I use (iBoxDB) doesn't have member attributes like [Ignore] that is available for SQLite. So all properties with supported types are stored in the database. However, such formatter properties shouldn't be stored if possible.
What I did - 1st try
I now transformed all properties to converters and most of the time this was no problem. However, ToAsShortTimeString not just uses one property but 3 to format the input. Therefore, in XAML I need to provide either those 3 properties to the value converter or the work unit itself which is bound to the page.
public class WorkUnitToEndTimeStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var workUnit = (WorkUnit) value;
if (workUnit.To.HasValue)
{
if (workUnit.Type == WorkUnitType.StartEnd)
return workUnit.To.Value.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
var duration = workUnit.To.Value - workUnit.From;
return DateHelper.FormatTime(duration, false);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
So I changed the binding of the Text property in the TextBlock that shows the formatted To property to the WorkUnit that is bound to the page.
<TextBlock
Grid.Column="2" Grid.Row="0"
Grid.ColumnSpan="2"
Text="{Binding WorkUnit,Converter={StaticResource WorkUnitToEndTimeStringConverter}}"
FontSize="28"
FontFamily="Segoe WP Light"
Foreground="{StaticResource TsColorWhite}"/>
Unfortunately, when the To property changes in the model, even though OnPropertyChanged is called (see model code above), the text block doesn't get updated. I assume the reason is that only those controls are updated where some property is directly bound to the changed model property.
What I did - 2nd try
So as I need 3 properties from WorkUnit in order to correctly format To I changed the binding as follows. I bound Text to WorkUnit.To and set the ConverterParameter to the WorkUnit itself. With this change I hoped that whenever To is changed in the model and the value converter is called, I can format the time because I have all the info provided from the converter parameter (WorkUnit). (I'm not printing the updated converter here but I changed it to accomodate the change on the value and parameter input parameters)
<TextBlock
Grid.Column="2" Grid.Row="0"
Grid.ColumnSpan="2"
Text="{Binding WorkUnit.To,Converter={StaticResource WorkUnitToEndTimeStringConverter},ConverterParameter={Binding WorkUnit}}"
FontSize="28"
FontFamily="Segoe WP Light"
Foreground="{StaticResource TsColorWhite}"/>
Unfortunately, in this case a XamlParseException exception is thrown.
{System.Windows.Markup.XamlParseException: Failed to assign to property 'System.Windows.Data.Binding.ConverterParameter'. [Line: 61 Position: 18] ---> System.InvalidOperationException: Operation is not valid due to the current state of the object.
at MS.Internal.XamlManagedRuntimeRPInvokes.TryApplyMarkupExtensionValue(Object target, XamlPropertyToken propertyToken, Object value)
at MS.Internal.XamlManagedRuntimeRPInvokes.SetValue(XamlTypeToken inType, XamlQualifiedObject& inObj, XamlPropertyToken inProperty, XamlQualifiedObject& inValue)
--- End of inner exception stack trace ---
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)}
Question
So is there a way to remove the formatter-property from my model so that I can keep my model as clean as possible? Is there sth. wrong with my converter? Is there any other way that I currently don't know of?
You could have a property in your WorkUnit called EndTimeString
public string EndTimeString
{
get
{
string endTime = null;
if (this.To.HasValue)
{
if (this.Type == WorkUnitType.StartEnd)
{
endTime = this.To.Value.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
}
else
{
var duration = this.To.Value - this.From;
endTime = DateHelper.FormatTime(duration, false);
}
}
return endTime
}
}
Of course, if To value changes, you want to notify the UI that the EndTimeString has also changed so that it picks up the new value:
public DateTime? To
{
get { return Get<DateTime?>(); }
set
{
Set(value);
OnPropertyChanged("To");
OnPropertyChanged("EndTimeString");
}
}
And then just bind straight to that string:
...Text="{Binding WorkUnit.EndTimeString}" />
Unfortunately you can't bind to parameters.
This would be easy with MultiBinding but that's not available for Windows Phone (as you stated in your comment).
You could implement it yourself but if you are not really into it :), there are implementations trying to mimic this behaviour. One of them can be found from the joy of code.
There is a NuGet package called Krempel's WP7 Library which has above implemented for WP7 but it works on WP8.x as well.
Downside is that it can only bind to elements in visual tree, so you have to use (for lack of a better word) relaying UI elements to get the job done. I have used similar technique myself when I can't bind directly to a property I need to. One case is AppBar, you can't bind directly to enabled property, so instead I use something like
<CheckBox Grid.Row="0" IsEnabled="{Binding AppBarEnabled}"
IsEnabledChanged="ToggleAppBar" Visibility="Collapsed" />
Anyway, below is full example, without any groovy patterns, on how you can achieve multibinding using above library. Try it out and see if it's worth the trouble. Options are that you have "extra" properties in your model or some extra elements and complexity in your view.
I used your WorkUnit model and Converter to make it more useful and easier to understand.
Outcome should be something like this.
MainWindow.xaml
<phone:PhoneApplicationPage
x:Class="WP8MultiBinding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Krempel.WP7.Core.Controls;assembly=Krempel.WP7.Core"
xmlns:conv="clr-namespace:WP8MultiBinding"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
shell:SystemTray.IsVisible="True">
<phone:PhoneApplicationPage.Resources>
<conv:WorkUnitToEndTimeStringConverter
x:Key="WorkUnitToEndTimeStringConverter" />
</phone:PhoneApplicationPage.Resources>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="80" />
<RowDefinition Height="80" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- Multibinding & converter -->
<controls:MultiBinding
x:Name="MultiBinding"
Converter="{StaticResource WorkUnitToEndTimeStringConverter}"
NumberOfInputs="3"
Input1="{Binding ElementName=Type, Path=Text, Mode=TwoWay}"
Input2="{Binding ElementName=From, Path=Text, Mode=TwoWay}"
Input3="{Binding ElementName=To, Path=Text, Mode=TwoWay}" />
<!-- Output from multibinded conversion -->
<TextBox Text="{Binding ElementName=MultiBinding, Path=Output}" Grid.Row="0" />
<!-- Update WorkUnit properties -->
<Button Click="UpdateButtonClick" Grid.Row="1">Test MultiBinding</Button>
<!-- Helper elements, might want to set visibility to collapsed -->
<StackPanel HorizontalAlignment="Center" Grid.Row="2">
<TextBlock x:Name="Type" Text="{Binding WorkUnit.Type, Mode=TwoWay}" />
<TextBlock x:Name="From" Text="{Binding WorkUnit.From, Mode=TwoWay}" />
<TextBlock x:Name="To" Text="{Binding WorkUnit.To, Mode=TwoWay}" />
</StackPanel>
</Grid>
</phone:PhoneApplicationPage>
MainWindow.xaml.cs
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using Microsoft.Phone.Controls;
namespace WP8MultiBinding
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
WorkUnit = new WorkUnit()
{
To = DateTime.Now.AddHours(5),
From = DateTime.Now,
Type = WorkUnitType.StartEnd
};
}
public WorkUnit WorkUnit { get; set; }
// Ensure bindings do update
private void UpdateButtonClick(object sender, RoutedEventArgs e)
{
WorkUnit.Type = WorkUnit.Type == WorkUnitType.StartEnd ?
WorkUnit.Type = WorkUnitType.Other :
WorkUnit.Type = WorkUnitType.StartEnd;
WorkUnit.From = WorkUnit.From.AddMinutes(60);
if (WorkUnit.To.HasValue)
WorkUnit.To = WorkUnit.To.Value.AddMinutes(30);
}
}
public enum WorkUnitType
{
StartEnd,
Other
}
public class WorkUnit : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private WorkUnitType _type;
private DateTime _from;
private DateTime? _to;
public WorkUnitType Type
{
get { return _type; }
set { _type = value; OnPropertyChanged(); }
}
public DateTime From
{
get { return _from; }
set { _from = value; OnPropertyChanged(); }
}
public DateTime? To
{
get { return _to; }
set { _to = value; OnPropertyChanged(); }
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
// Multivalue Converter
public class WorkUnitToEndTimeStringConverter : Krempel.WP7.Core.Controls.IMultiValueConverter
{
private const string DateFormat = "M/d/yyyy h:mm:ss tt";
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Index: 0 = Type, 1 = From, 2 = To
if (values[2] != null)
{
var type = (WorkUnitType) Enum.Parse(typeof (WorkUnitType), values[0].ToString());
var from = DateTime.ParseExact(values[1].ToString(), DateFormat, CultureInfo.InvariantCulture);
var to = DateTime.ParseExact(values[2].ToString(), DateFormat, CultureInfo.InvariantCulture);
if (type == WorkUnitType.StartEnd)
return to.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
var duration = to - from;
return duration; // DateHelper.FormatTime(duration, false);
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

How to do multiple items Data binding in WPF

I have a particular scenarios. My application looks like this.
In the left side there are some User list Which is a ListBox and at the right side few fields which are data binding to left side. How it works is, if you select "User 1" in the right side user 1 related information will appear and you can modify the information and its is data binding with "UpdateSourceTrigger=PropertyChanged" so it immediately reflects at the left side too. Same case for other users.
Now the problem is if I select multiple users and edit a field say Field 3 which is Editable a textBox. Now If I select user 1 and edit this textbox it reflects in the user 1 "Note: ... " and if I select user 2 and edit the Field 3 it updates the User 2 "Note: ... " but in case of multi selection How do I achieve it? Suppose I want to select user 1 and User 2 both and Edit the Note field It should update both the note fields of user 1 and user 2 and Data binding should also work I mean it should immediately the text i am entering into the textbox. Any ideas how can I achieve this?
Currently in my viewModel
Model
public String Note
{
get
{
return (String)GetValue(NoteProperty);
}
set { SetValue(NoteProperty, value); }
}
View
and in XAML the User ListBox Items template is defined like this
<TextBlock Text="{Binding Note, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
and in the XAML the rightside textbox (field 3) is data bound in the same manner
<TextBox Text="{Binding Note, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
How do I achieve multiple users data binding?
Please help and give me some ideas.
EDIT:
Converter:
public class MultiBindingConverter : IValueConverter
{
ObservableCollection<Info> mycollection;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var coll = (ObservableCollection<Info>)value;
mycollection = coll;
if (coll.Count == 1)
{
if (parameter.ToString() == "FNote")
return coll[0];
}
else if (coll.Count > 1)
{
// string name = coll[0].FirstName;
if (parameter.ToString() == "FNote")
{
string name = coll[0].Note;
foreach (var c in coll)
{
if (c.Note != name)
return null;
else continue;
}
return name;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter.ToString() == "FNote")
{
foreach (var c in mycollection)
{
c.Note = value.ToString();
}
return mycollection;
}
return null;
}
}
For me only one TextBox Editable NoteTextBox needs to to be DataBinded with multiple Users.
In my ViewModel
I have written
ViewModel
private Command selectionChangedCommand;
public Command SelectionChangedCommand
{
get
{
if (selectionChangedCommand == null)
{
selectionChangedCommand = new Command(SelectionChanged, true);
}
return selectionChangedCommand;
}
set { selectionChangedCommand = value; }
}
public void SelectionChanged(object value)
{
selectedItem = new ObservableCollection<Info>((value as IEnumerable).OfType<Info>());
}
private ObservableCollection<Info> selectedItem;
public ObservableCollection<Info> SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
PropertyChanged("SelectedItem");
}
}
In the Info class there is one property Note which needs to be binded to the View's two places.
I fully agree with #GazTheDestroyer ... this kind of Data Binding can not be achieved through Data binding alone. What #Kumar has suggested is working as a POC, but when you are in a live project and you play with model, viewModel and view and many UserControl with one view model or one User control with two ViewModels, then the difficulty of achieving this scenario is beyond guessing.
Ok, no more theory. I have achieved this and I am going to share how I did so.
One-to-one DataBinding is perfect and working fine. When you select User 4 This user Note field and Field3 Editable NoteBox are bound to the same Property, so it works perfectly.
In multiple selection say User4 is selected first, then you select User3 and user1, I put a logic in code behind that when multiple items are selected Note text is empty. This is not against
MVVM as updating a view based on some criteria of view is not breaking MVVM pattern. So now when the editable text box is updated with some text user4 properties is updated in viewModel. Now the difficult part is to update the other selected users. Here is the code that will update the selected users and will reflect as I have mentioned Mode="TwoWay", UpdateSourceTriger="PropertyChanged"
if (listUser.SelectedItems.Count > 1)
{
for (int i = 0; i < listUser.SelectedItems.Count; i++)
{
Info info = listUser.SelectedItems[i] as Info;
info.Note = (string)tbNote.Text;
}
}
In this way the value of the Editable note textbox is updated in the properties of all the users Note Property and as the binding is two-way, it will reflect in other users too.
There might be many way to solve it, but I found this way and it's working superbly, so I thought I'd answer my own question.
You cannot achieve this via databinding alone, since there are situations where you need to make logical decisions.
For instance, if user1 and user2 have different notetext, then when both are selected you cannot show both at the same time. Instead I guess you want some method of specifying that you want to "keep original text", or allow user to over type to set both texts to be the same.
Whatever you intend, you need to have separate binding sources in your viewmodel so that you can update them independently and make logical decisions.
I tried something with i know and i got output just as your requirement.Please correct me if i'm wrong.
XAML
<Window x:Class="MVVM_sample_ListBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVM_sample_ListBox"
Title="MainWindow" Height="350" Width="525"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<Window.Resources>
<local:Converter x:Key="Converter"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="235*" />
<ColumnDefinition Width="268*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="lb" SelectionMode="Multiple" Grid.Row="0" ItemsSource="{Binding MyCollection}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseUp" >
<i:InvokeCommandAction CommandParameter="{Binding SelectedItems, ElementName=lb}" Command="{Binding SelectionChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding SecondName}"/>
<TextBlock Text="{Binding Company}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" >
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=FName, Converter={StaticResource Converter}}" Name="textBox1" VerticalAlignment="Top" Width="120" />
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=SName, Converter={StaticResource Converter}}" Name="textBox2" VerticalAlignment="Top" Width="120" />
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=Comp, Converter={StaticResource Converter}}" Name="textBox3" VerticalAlignment="Top" Width="120" />
</StackPanel>
</Grid>
</Window>
C#
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
Model
public class Model : INotifyPropertyChanged
{
private string fname;
public string FirstName
{
get { return fname; }
set { fname = value;RaisePropertyChanged("FirstName"); }
}
private string sname;
public string SecondName
{
get { return sname; }
set { sname = value; RaisePropertyChanged("SecondName");}
}
private string company;
public string Company
{
get { return company; }
set { company = value;RaisePropertyChanged("Company"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if(PropertyChanged!= null)
{
this.PropertyChanged(this,new PropertyChangedEventArgs(name));
}
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private MyCommand selectionChangedCommand;
public MyCommand SelectionChangedCommand
{
get
{
if (selectionChangedCommand == null)
{
selectionChangedCommand = new MyCommand(SelectionChanged);
}
return selectionChangedCommand;
}
set { selectionChangedCommand = value; }
}
public void SelectionChanged(object value)
{
SelectedItem = new ObservableCollection<Model>((value as IEnumerable).OfType<Model>());
}
private ObservableCollection<Model> selectedItem;
public ObservableCollection<Model> SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; RaisePropertyChanged("SelectedItem"); }
}
private ObservableCollection<Model> mycoll;
public ObservableCollection<Model> MyCollection
{
get { return mycoll;}
set { mycoll = value;}
}
public ViewModel()
{
SelectedItem = new ObservableCollection<Model>();
SelectedItem.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(SelectedItem_CollectionChanged);
MyCollection = new ObservableCollection<Model>();
MyCollection.Add(new Model { FirstName = "aaaaa", SecondName = "bbbbb", Company = "ccccccc" });
MyCollection.Add(new Model { FirstName = "ddddd", SecondName = "bbbbb", Company = "eeeeeee" });
MyCollection.Add(new Model { FirstName = "fffff", SecondName = "gggggg", Company = "ccccccc" });
}
void SelectedItem_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//this.SelectedItem =new ObservableCollection<Model>((sender as ObservableCollection<Model>).Distinct());
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if(PropertyChanged!= null)
{
this.PropertyChanged(this,new PropertyChangedEventArgs(name));
}
}
}
public class MyCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canexecute;
public MyCommand(Action<object> execute, Predicate<object> canexecute)
{
_execute = execute;
_canexecute = canexecute;
}
public MyCommand(Action<object> execute)
: this(execute, null)
{
_execute = execute;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (parameter == null)
return true;
if (_canexecute != null)
{
return _canexecute(parameter);
}
else
{
return true;
}
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion
}
Converter
public class Converter : IValueConverter
{
ObservableCollection<Model> mycollection;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var coll = (ObservableCollection<Model>)value;
mycollection = coll;
if (coll.Count == 1)
{
if (parameter.ToString() == "FName")
return coll[0].FirstName;
else if (parameter.ToString() == "SName")
return coll[0].SecondName;
else if (parameter.ToString() == "Comp")
return coll[0].Company;
}
else if(coll.Count >1)
{
// string name = coll[0].FirstName;
if (parameter.ToString() == "FName")
{
string name = coll[0].FirstName;
foreach (var c in coll)
{
if (c.FirstName != name)
return null;
else continue;
}
return name;
}
if (parameter.ToString() == "SName")
{
string name = coll[0].SecondName;
foreach (var c in coll)
{
if (c.SecondName != name)
return null;
else continue;
}
return name;
}
if (parameter.ToString() == "Comp")
{
string name = coll[0].Company;
foreach (var c in coll)
{
if (c.Company != name)
return null;
else continue;
}
return name;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter.ToString() == "FName")
{
foreach (var c in mycollection)
{
c.FirstName = value.ToString();
}
return mycollection;
}
else
if (parameter.ToString() == "SName")
{
foreach (var c in mycollection)
{
c.SecondName = value.ToString();
}
return mycollection;
}
else
if (parameter.ToString() == "Comp")
{
foreach (var c in mycollection)
{
c.Company = value.ToString();
}
return mycollection;
}
return null;
}
}

C# WPF - ComboBox DataBinding

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.

Categories