I'm trying to learn some data-binding in WPF, so have started building a little condition builder test application, but am having some trouble figuring out my binding.
So I have a set of conditions that are based upon generics. This is the class structure with just a simple example of a BooleanCondition.
public abstract class AbstractCondition
{
/// <summary>
/// Gets the entry mode.
/// </summary>
/// <value>The entry mode.</value>
public abstract InputFieldMode InputFieldMode { get; }
}
public abstract class GenericCondition<T, O> : AbstractCondition
{
/// <summary>
/// Checks the specified value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns></returns>
public abstract Boolean Check(T value);
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public object Value
{
get;
set;
}
/// <summary>
/// Gets or sets the options.
/// </summary>
/// <value>The options.</value>
public O Option
{
get;
set;
}
/// <summary>
/// Gets the available options.
/// </summary>
/// <value>The available options.</value>
public abstract ObservableCollection<O> AvaliableOptions { get; }
}
/// <summary>
/// Describes a Condition that operations on a Boolean
/// </summary>
/// <typeparam name="U"></typeparam>
public class BooleanCondition : GenericCondition<Boolean?, BooleanConditionOption>
{
/// <summary>
/// Initializes a new instance of the <see cref="BooleanCondition<U>"/> class.
/// </summary>
/// <param name="option">The option.</param>
public BooleanCondition(BooleanConditionOption option)
{
this.Option = option;
}
/// <summary>
/// Checks the specified value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns></returns>
public override bool Check(bool? value)
{
if (value.HasValue)
{
switch (this.Option)
{
case BooleanConditionOption.IsFalse:
return !value.Value;
case BooleanConditionOption.IsTrue:
return value.Value;
}
}
return false;
}
/// <inheritdoc />
public override InputFieldMode InputFieldMode
{
get { return InputFieldMode.NoField; }
}
/// <inheritdoc />
public override ObservableCollection<BooleanConditionOption> AvaliableOptions
{
get
{
var options = Enum.GetValues(typeof(BooleanConditionOption));
var optionsCollection = new ObservableCollection<BooleanConditionOption>(options.Cast<BooleanConditionOption>());
return optionsCollection;
}
}
}
There are 4 Conditions currently, the basic idea behind this, is that a Condition operates on a type of Object, and provides a set of 'ConditionOptions' that are particular for that type (Bool has isTrue, isFalse, whereas a number might have < <= > >= etc).
So now I'm trying to create a view to make the Condition easier to consume on the UI. An Update is called with a selected value from a combo, that has a datatype. At that point I construct the appropriate condition, and want to expose a collection of ConditionOptions via the ConditionValues property (so the results of an enum, but it could be anyone of 4).
public class ConditionView
{
/// <summary>
/// Gets the Fields that are available for selection.
/// </summary>
public ObservableCollection<IDataField> Fields { get; set; }
public ObservableCollection<Object> ConditionValues { get; set; }
/// <summary>
/// Gets the Condition that has been selected.
/// </summary>
public AbstractCondition SelectedCondition { get; private set; }
/// <summary>
/// Update various options based upon the Selection
/// </summary>
/// <param name="field">The IDataField selected</param>
public void Update(IDataField field)
{
if (field != null)
{
// Determine what sort of condition we need
switch (field.Type)
{
case DataType.Boolean:
BooleanCondition booleanCondition = new BooleanCondition(BooleanConditionOption.IsFalse);
this.SelectedCondition = booleanCondition;
this.ConditionValues = booleanCondition.AvaliableOptions;
break;
case DataType.String:
this.SelectedCondition = new StringCondition(StringConditionOption.Contains);
break;
case DataType.Numeric:
this.SelectedCondition = new NumericCondition(NumericConditionOption.Equals);
break;
case DataType.Date:
this.SelectedCondition = new DateCondition(DateConditionOption.Equals);
break;
default:
throw new InvalidOperationException("Unknown Data Type");
}
}
}
I'm currently just trying to get the Boolean one going, but I'm not sure if ObservableCollection is what I should be binding to (I get a build error trying: "Error 5 Cannot implicitly convert type 'System.Collections.ObjectModel.ObservableCollection' to 'System.Collections.ObjectModel.ObservableCollection'). How should I be approaching this binding issue? Ultimately I've got some Convertors that are going to expect to be working on an enum too, so I'm not sure just casting the results to Objects is right :S
Update:
Trying to bind 'Fields' to a ComboBox, and once it varies a call through to Update will modify 'ConditionValues' which is bound to another ComboBox.
<UserControl x:Class="ConditionBuilder.Views.Condition"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:cb="clr-namespace:ConditionBuilder"
xmlns:wm="clr-namespace:Watermark"
>
<UserControl.Resources>
<cb:EnumToUIConvertor x:Key="enumItemsConverter"/>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="229"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbField" Grid.Column="0" Height="22" Width="120" ItemsSource="{Binding Path=Fields}" SelectionChanged="cmbField_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=Name, Mode=OneWay}" Height="Auto" Margin="-5" VerticalAlignment="Stretch"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<wm:Watermark.WatermarkContent>
<Label Padding="0">Select Field...</Label>
</wm:Watermark.WatermarkContent>
</ComboBox>
<ComboBox x:Name="cmbCondition" Grid.Column="1" Height="22" Width="120" ItemsSource="{Binding Path=ConditionValues, Mode=OneWay}">
<wm:Watermark.WatermarkContent>
<Label Padding="0">Select Condition...</Label>
</wm:Watermark.WatermarkContent>
</ComboBox>
</Grid>
</UserControl>