tags:

views:

114

answers:

1

I have a dialog with a few fields. Some of them use validation - ValidatesOnExceptions=True in my case.

I have a data object with INotifyPropertyChanged that I bind to dialogs' DataContext.

I also have a "Save" button. Validation works fine, but only after I edit each field.

If dialog is opened and closed immediately, validation will not fire.

What should I do prevent Save from firing in this case (use case: open dialog, press save immediately)

+2  A: 

Use command bindings for the save button, then you can enable/disable the button depending on your current state

see this simple tutorial and if you want further explantion the msdn article, also josh smith gets more in depth

We handle your above situation by using a combination of commands and an IsValid property on the underlying model we are binding to. We do validation at the business model level (some times in the ui as well) and when the business model is valid we enable the command, or as in your case, save button.

Here is a sample of the style we apply to our text boxes (we derive from text box and give it another property called SimpleField. This field has the properties IsValid, IsDirty, IsReadOnly, ErrorMessage and DatabaseValue. This enables us to know if the field is valid, whether it has changed if it is read only (i.e. the user doesnt have the permission to change the value or it is locked for another reason), if there is an error message (associated with the IsValid property) and also the database value (for when the field has changed, the user can see the original value) We use all of these properties in the style below

   <!-- Simple TextBox -->
   <Style
      TargetType="{x:Type local:SimpleFieldTextBox}"
      BasedOn="{StaticResource {x:Type TextBox}}">
      <Setter
         Property="KeyboardNavigation.TabNavigation"
         Value="None" />
      <Setter
         Property="FocusVisualStyle"
         Value="{x:Null}" />
      <Setter
         Property="AllowDrop"
         Value="True" />
      <Setter
         Property="SnapsToDevicePixels"
         Value="True" />
      <Setter
         Property="Height"
         Value="22" />
      <Setter
         Property="Template">
         <Setter.Value>
            <ControlTemplate
               TargetType="{x:Type local:SimpleFieldTextBox}">

               <Border
                  x:Name="PART_SimpleFieldTextBox"
                  Background="{TemplateBinding Background}"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  BorderThickness="{TemplateBinding BorderThickness}"
                  Height="{TemplateBinding Height}"
                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">

                  <Grid>
                     <Grid.ColumnDefinitions>
                        <ColumnDefinition
                           Width="Auto" />
                        <ColumnDefinition
                           Width="*" />
                     </Grid.ColumnDefinitions>

                     <!-- The implementation places the Content into the ScrollViewer.
                          It must be named PART_ContentHost for the control to function -->
                     <ScrollViewer
                        x:Name="PART_ContentHost"
                        Grid.Column="1"
                        Margin="0" />


                     <!-- Not Valid Icon -->
                     <Path
                        x:Name="IconError"
                        Grid.Column="0"
                        Fill="Red"
                        Stretch="Fill"
                        Margin="1,1,4,1"
                        HorizontalAlignment="Left"
                        VerticalAlignment="Top"
                        Visibility="Collapsed"
                        Width="4"
                        Height="14"
                        SnapsToDevicePixels="True"
                        Data="M0,11 L6,11 6,14 0,14 z M0,0 L6,0 6,10 0,10 z">
                        <Path.ToolTip>
                           <ToolTip>
                              <StackPanel
                                 Orientation="Vertical"
                                 MaxWidth="300"
                                 MaxHeight="100">
                                 <TextBlock
                                    FontStyle="Italic"
                                    Text="Error:" />
                                 <TextBlock
                                    Margin="8,0,0,0"
                                    TextWrapping="WrapWithOverflow"
                                    TextTrimming="CharacterEllipsis"
                                    Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SimpleField.ErrorMessage}" />
                                 <TextBlock
                                    Margin="0,4,0,0"
                                    FontStyle="Italic"
                                    Text="Original Value: " />
                                 <TextBlock
                                    Margin="8,0,0,0"
                                    TextWrapping="WrapWithOverflow"
                                    TextTrimming="CharacterEllipsis"
                                    Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SimpleField.DatabaseValue}" />
                              </StackPanel>
                           </ToolTip>
                        </Path.ToolTip>
                     </Path>


                     <!-- Valid (but changed) Icon-->
                     <Path
                        x:Name="IconWarning"
                        Grid.Column="0"
                        Fill="#FF5BBD30"
                        Stretch="Fill"
                        Margin="1,1,0,0"
                        HorizontalAlignment="Left"
                        VerticalAlignment="Top"
                        Visibility="Collapsed"
                        Width="8"
                        Height="8"
                        SnapsToDevicePixels="True"
                        Data="M0,0 L8,0 0,8 z">
                        <Path.ToolTip>
                           <ToolTip>
                              <StackPanel
                                 Orientation="Vertical"
                                 MaxWidth="500"
                                 MaxHeight="100">
                                 <TextBlock
                                    Text="Original Value: " />
                                 <TextBlock
                                    Margin="8,0,0,0"
                                    TextWrapping="Wrap"
                                    TextTrimming="CharacterEllipsis"
                                    Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SimpleField.DatabaseValue}" />
                              </StackPanel>
                           </ToolTip>
                        </Path.ToolTip>
                     </Path>
                  </Grid>
               </Border>
               <ControlTemplate.Triggers>

                  <!-- Stop the text box being edited if the simple field is read only -->
                  <DataTrigger
                     Binding="{Binding RelativeSource={RelativeSource Self}, Path=SimpleField.IsReadOnly}"
                     Value="True">
                     <Setter
                        Property="IsReadOnly"
                        Value="True" />
                     <Setter
                        Property="Foreground"
                        Value="{StaticResource DisabledForegroundBrush}" />
                     <Setter
                        TargetName="PART_SimpleFieldTextBox"
                        Property="Background"
                        Value="{StaticResource DisabledBackgroundBrush}" />
                     <Setter
                        TargetName="PART_SimpleFieldTextBox"
                        Property="BorderBrush"
                        Value="{StaticResource DisabledBorderBrush}" />
                  </DataTrigger>

                  <!-- IsEnabled condition -->
                  <DataTrigger
                     Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled}"
                     Value="False">
                     <Setter
                        Property="Foreground"
                        Value="{StaticResource DisabledForegroundBrush}" />
                     <Setter
                        TargetName="PART_SimpleFieldTextBox"
                        Property="Background"
                        Value="{StaticResource DisabledBackgroundBrush}" />
                     <Setter
                        TargetName="PART_SimpleFieldTextBox"
                        Property="BorderBrush"
                        Value="{StaticResource DisabledBorderBrush}" />
                  </DataTrigger>

                  <!-- When value inside field has been changed -->
                  <DataTrigger
                     Binding="{Binding RelativeSource={RelativeSource Self}, Path=SimpleField.IsDirty}"
                     Value="True">
                     <Setter
                        TargetName="IconWarning"
                        Property="Visibility"
                        Value="Visible" />
                  </DataTrigger>

                  <!-- When value inside field is NOT valid -->
                  <DataTrigger
                     Binding="{Binding RelativeSource={RelativeSource Self}, Path=SimpleField.IsValid}"
                     Value="False">
                     <Setter
                        TargetName="IconWarning"
                        Property="Visibility"
                        Value="Collapsed" />
                     <Setter
                        TargetName="IconError"
                        Property="Visibility"
                        Value="Visible" />
                  </DataTrigger>
               </ControlTemplate.Triggers>
            </ControlTemplate>
         </Setter.Value>
      </Setter>
   </Style>
Aran Mulholland
I usually use commands but didn't want them here as dialog is simple but maybe there is no way around that. What do you use for validation?
bh213
we use our own custom validation, in the business layer, wpf validation is fine if all you are trying to do is very simple validation, but what about when you have multiple rules, for example a dialog box with three related fields. in this case its easier to validate in code and have a Boolean property that you set when all is valid. then have your command handler look at this property.
Aran Mulholland
How do you provide feedback back to the user to show what input is wrong if you are using model validation? Do you have a sample?
bh213
added some extra stuff to show how we do validation
Aran Mulholland