0

I am creating a window that consists of dynamically created controls. I'm creating them using an ItemsControl:

<ItemsControl Grid.Row="0" Name="DynamicContent" ItemsSource="{Binding Path=EmbeddedInputControls}" ItemTemplateSelector="{Binding Path=EmbeddedInputControlsTemplateSelector}">
      <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
              <WrapPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
     </ItemsControl.ItemsPanel>
</ItemsControl>

Where the ItemTemplateSelector selects a DataTemplate based on type from resources:

<Window.Resources>
  <converters:ErrorBooleanToBrush x:Key="ErrorBooleanToBrush"/>
  <DataTemplate x:Key="EmbeddedStringInput" DataType="embeddedInputDescriptors:StringEmbeddedInputDescriptor">
      <StackPanel Orientation="Horizontal" Background="{Binding IsErrored, Converter={StaticResource ErrorBooleanToBrush}}">
         <Label Content="{Binding LabelContent}"></Label>
         <dxe:TextEdit EditValue="{Binding TextValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="{Binding IsReadOnly}" />
      </StackPanel>
   </DataTemplate>
   <DataTemplate x:Key="EmbeddedIntegerInput" DataType="embeddedInputDescriptors:IntegerEmbeddedInputDescriptor">
        <StackPanel Orientation="Horizontal" Name="IntStackPanel">
            <Label Content="{Binding LabelContent}"></Label>
            <dxe:SpinEdit Value="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="{Binding IsReadOnly}"
                      MinValue="{Binding MinValue}" MaxValue="{Binding MaxValue}" Validate="OnValidate" Tag="{Binding Self}"/>
        </StackPanel>
    </DataTemplate>
</Window.Resources>

This works fine for display but validation has proven to be a headache. The solution I've finally found uses code-behind and the OnValidate event handler:

private void OnValidate(object sender, ValidationEventArgs e)
{
    var dynamicInputElement=((FrameworkElement)sender).Tag as IEmbeddedInputDescriptor;

    var errorContent = dynamicInputElement?.ErrorContent(e.Value);

    if(!string.IsNullOrEmpty(errorContent))
     {
        e.IsValid = false;
        e.ErrorContent = errorContent;
     }
}

It's the only code-behind I've had to write and I'd like to get rid of it but I just can't see how to implement validation without it. The validation rules are going to be based on business logic so I can't have them in the XAML. I almost got a solution when I gave each DataTemplate a style whose members were obtained from the associated descriptor but it was a bit ugly:

{Binding IsErrored, Converter={StaticResource ErrorBooleanToBrush}}
<StackPanel.Style>
   <Style>
     <Style.Triggers>
         <DataTrigger Binding="{Binding Path=IsErrored}" Value="True">
               <Setter Property="StackPanel.Background" Value="LightCoral">
                    </Setter>
         </DataTrigger>
      </Style.Triggers>
    </Style>
</StackPanel.Style>

The objects used to derive the framework elements are:

internal interface IEmbeddedInputDescriptor
{
    bool IsReadOnly { get; }
    bool IsRequired { get; }
    object Value { get; }
    bool IsErrored { get; }
    string ErrorContent(object value);
}

It'd be nice to get rid of the code-behind and I'm sure I'd learn more useful stuff about WPF in the process.

More info: Re binding validations:

<dxe:SpinEdit Value="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidationRules={Binding valRules}}"

and

public IEnumerable<ValidationRule> valRules
{
    get { throw new NotImplementedException(); }
}

Results in the error 'A 'Binding' cannot be used within a 'ValidationRuleCollection' collection. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.'.

I had problems with IDataErrorInfo as well. I never managed to get either property to be called whether I implemented it in my main viewmodel or in the descriptors themselves. A possible problem should I get that far is how to identify the error property as I suspect it will always be 'Value' which is ambiguous. In fact that was the problem with most solutions the ones that yielded an event I could respond to left me with no context to work from.

That's why my current solution resorts to using the Tag property. Without that it's hard to link a form event back to the underlying business object.

Meer
  • 2,765
  • 2
  • 19
  • 28
Andrue Cope
  • 249
  • 3
  • 8
  • I think you might be looking for [`Binding.ValidationRules`](https://msdn.microsoft.com/en-us/library/ms753962%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396). You could set that up in the `DataTemplate`s. There's still C# code, but it's factored in a MVVMish way. You could also look at [`IDataErrorInfo`](http://stackoverflow.com/questions/14023552/how-to-use-idataerrorinfo-error-in-a-wpf-program), which puts validation in the viewmodels. – 15ee8f99-57ff-4f92-890c-b56153 Feb 02 '17 at 16:13
  • ["I broke the example code!"](http://i1.kym-cdn.com/photos/images/facebook/001/016/682/379.jpeg) OK. "And now it doesn't work!" Yes, that's the usual outcome. Anything else I can help you with? – 15ee8f99-57ff-4f92-890c-b56153 Feb 02 '17 at 17:16
  • Eh? What are you suggesting now? I have better things to do than read pointless posts. – Andrue Cope Feb 02 '17 at 20:55
  • I linked example code on MSDN. You posted some strange ValidationRules code that can't possibly work, and certainly wasn't in the example. I don't even understand your objection to `IDataErrorInfo`; no class has more than one property named `Value` (or anything else). Anyway nobody can help you with that without seeing the code. – 15ee8f99-57ff-4f92-890c-b56153 Feb 02 '17 at 21:10
  • I'm using several base classes and the one at the bottom does have a 'Value' property. Being told at that 'Value' has changed is therefore ambiguous when trying to identify what has been modified. – Andrue Cope Feb 03 '17 at 08:42

1 Answers1

1

The answer turns out to be related to the way I implemented my descriptor classes (the objects that become the datacontext for each created framework element). I implemented IDataErrorInfo on the 'wrong' base class which it meant wasn't visible in the class hierarchy. With fresh eyes after a good night's sleep I spotted it almost immediately.

So often the way.

Andrue Cope
  • 249
  • 3
  • 8