Validation order for an entire form of controls - c#

I'm learning this wpf stuff and trying to get my head around validation of controls. Specifically what I'm looking for is this...
A form can have 100 controls on it (exaggerating, but possible). The form's layout and flow are a specific order (via tabbed sequence for user). A user may never get to some "required" fields and click on a "Save" button. How can I trigger it so all the controls force triggering their own respective "Validation" events.
Based on above, does the WPF framework process the validation rules in the tab order the user is looking at. If not, how can that be controlled to match the data entry flow instead of bouncing around in the sequential order the application happens to create objects and their respective validation rules.
Is there a way to have ALL failed controls triggered for the default behavior of putting a red border box around the failed control instead of only one at a time.
Thanks

Typically, to accomplish what you are looking for you use an MVVM type pattern. This means that you bind each control that collects data in your WPF form to a backing field or property. You add validation to the binding, with a style that will cause the red border box. For controls with required data, part of the validation is that they are filled in. You could define a single validation rule for this called "ValidWhenHasData" or some such.
To cause the validations to trigger only when you press "save" or the like, there are a number of ways you can do this. I typically make a property in each validation rule called "IsEnabled" and set it to false by default; if set to false, the validation rule always returns valid. I then add a list in the code-behind of the controls that I want to validate. When "save" is clicked, I go through the list and set all the validation rules' IsEnabled to true, clear all errors on the controls in the list, and then refresh the binding on each. This will display the red rectangles on any that are not filled in or whatever else you have defined as an error condition. You can also use this list to set focus to the first control that failed validation, in the order you choose.
Example validation control template, which includes placeholder for validation error tooltip:
<ControlTemplate x:Key="errorTemplate">
<Canvas Width="{Binding Path=AdornedElement.ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}" Height="{Binding Path=AdornedElement.ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder/>
</Border>
<Border Canvas.Top="-5" Canvas.Right="-5" BorderBrush="Gray" BorderThickness="1" >
<TextBlock x:Name="errorBlock" TextAlignment="Center" Background="Red" Foreground="White" Width="10" Height="10" FontSize="9" ctl:valTooltip.MessageBody="{Binding Path=AdornedElement.(Validation.Errors)[0].ErrorContent,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}">*</TextBlock>
</Border>
</Canvas>
</ControlTemplate>
Example validation binding:
<TextBox x:Name="TBNumItems" Margin="2,2,2,2" MinWidth="40" HorizontalAlignment="Left" Validation.ErrorTemplate="{StaticResource errorTemplate}">
<TextBox.Text>
<Binding x:Name="NumItemsBinding" Path="NumItems" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<cal:UIntValidationRule x:Name="NumItemsValidationRule" MinValue="1" MaxValue="99999" IsEnabled="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Example code behind for validation:
/// <summary>
/// Clears all validation errors
/// </summary>
void ClearAllValidationErrors()
{
Validation.ClearInvalid(TBNumItems.GetBindingExpression(TextBox.TextProperty));
}
/// <summary>
/// Revalidates everything
/// </summary>
void RevalidateAll()
{
ClearAllValidationErrors();
TBNumItems.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}

Make your data object implement IDataErrorInfo, which will perform a validation check on a property when the user changes it, then use the following style to apply the red border to controls that have a validation error:
<!-- ValidatingControl Style -->
<Style TargetType="{x:Type FrameworkElement}" x:Key="ValidatingControl">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding
Path=(Validation.Errors)[0].ErrorContent,
RelativeSource={x:Static RelativeSource.Self}}" />
</Trigger>
</Style.Triggers>
</Style>
This will
Only perform a validation check for a single property when that property gets changed
Only (and always) show the red validation error border on controls that are bound to an invalid property
Edit
Here's a sample of a how I would implement validation on an object:
public class MyObject : ValidatingObject
{
public MyObject()
{
// Add Properties to Validate here
this.ValidatedProperties.Add("SomeNumber");
}
// Implement validation rules here
public override string GetValidationError(string propertyName)
{
if (ValidatedProperties.IndexOf(propertyName) < 0)
{
return null;
}
string s = null;
switch (propertyName)
{
case "SomeNumber":
if (SomeNumber <= 0)
s = "SomeNumber must be greater than 0";
break;
}
return s;
}
}
And my ValidatingObject base class which implements IDataErrorInfo usually contains the following:
#region IDataErrorInfo & Validation Members
/// <summary>
/// List of Property Names that should be validated
/// </summary>
protected List<string> ValidatedProperties = new List<string>();
public abstract string GetValidationError(string propertyName);
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public bool IsValid
{
get
{
return (GetValidationError() == null);
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (error != null)
{
return error;
}
}
}
return error;
}
#endregion // IDataErrorInfo & Validation Members

I faced the same problem. I wanted controls who know if they are required and report automatically any change to the hosting Window. I didn't want to have to write complicated XAML or other code, just placing the control and setting a property to indicate if user input is required. The control searches then automatically the host window and informs it when the user keys in required data or deletes it, so the window can change the state of the Save button. The final solution looks like this:
<wwl:CheckedWindow x:Class="Samples.SampleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wwl="clr-namespace:WpfWindowsLib;assembly=WpfWindowsLib">
<StackPanel>
<wwl:CheckedTextBox x:Name="TestCheckedTextBox" MinWidth="100" MaxLength="20" IsRequired="True"/>
<Button x:Name="SaveButton" Content="_Save"/>
</StackPanel>
</wwl:CheckedWindow>
For this to work added to the most common WPF controls an IChecker, which does the following:
know when the data has changed
know when the data has been unchanged (user undid his change)
know when a "required" control is lacking data
know when a "required" control has data
find automatically the window the control is in and inform the Window about each state change
If the window gets informed by any control that the control's state has changed, the window then queries automatically all other controls about their state. If all required controls have data, the Save Button gets enabled.
Knowing that the user has changed some data it is useful, when the user tries to close the window without saving . He gets then automatically a warning and gives him the choice if he wants to save or discard the data.
I had to write too much code to be posted here, but now life is very easy. Just place the control into the XAML and add few lines of code to the Window for the Save Button, that's all. Here is an detailed explanation: https://www.codeproject.com/Articles/5257393/Base-WPF-Window-functionality-for-data-entry
The code is on GitHub: https://github.com/PeterHuberSg/WpfWindowsLib

Related

how to write a custom setter for a dependency object in wpf using mvvm

how to write a custom setter for a dependency object in wpf using mvvm ?
In my ViewModel I have a dependency object called Seasonalprop which I use to bind to a TextBox in XAML. I would like to write a custom setter, so that it notifies the user when the provided string input cannot be converted to double.
The error that I am getting is that value is a string and cannot be converted to double.
public double Seasonalprop
{
get { return (double)GetValue(SeasonalProperty); }
set
{
try
{
Double.TryParse(value, out parsedouble);
SetValue(SeasonalProperty, value);
}
catch(Exception ex)
{
MessageBox.Show(" String Input cannot be converted to
type double");
}
}
}
I think you want to write a custom settor so that it notifies the user if the value in text box is invalid.
Have a look at the docs for validation in WPF
The text content of the TextBox in the following example is bound to
the Age property (of type int) of a binding source object named ods.
The binding is set up to use a validation rule named AgeRangeRule so
that if the user enters non-numeric characters or a value that is
smaller than 21 or greater than 130, a red exclamation mark appears
next to the text box and a tool tip with the error message appears
when the user moves the mouse over the text box.
I think your whole concept was gone to a wrong direction. First of all, when a binding expression updates a dependency property, it will call its onwer's SetValue method but not its clr property wrapper. So a custom setter would do nothing for you in this situation. And as #peeyushsingh's answer, wpf has binding validation for this. So, something you need should be like:
<TextBox Text="{Binding Seasonalprop, ValidatesOnExceptions=True}">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<Border BorderThickness="1" BorderBrush="Red" >
<AdornedElementPlaceholder/>
</Border>
<TextBlock Foreground="Red" Margin="2" Name="cc"
Text="! Not a double."/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>

WPF MVVM: Switch from one User Control to Another on button click

I've been slowly playing around with MVVM and the concept makes since. I have an application window and 2 user controls with viewModels attached to each. I want to click on a button within the first user control and get taken to the second user control.
I've looked at a few tutorials such as:
Example 1Example 2
Both those change screens from within the main window view model but I'd like to do it within the user control itself. Is it possible to either pass a command to change windows back to the main application view model, or have the user control model change the view on button click.
Edit: I figure I need to pass it as a command but I'm not sure how to pass which view I want along with it.
In MVVM, commands generally work at ViewModel level and have nothing to do with (or have any knowledge of) the View layer. To achieve what you have described, create a public property that controls the type of View you want to see. For example (using MVVM Light):
class YourViewModel : ViewModelBase
{
public string ActiveView
{
get { return _ApplicationMessage; }
//Note that this setter performs notification
set { Set(ref _ApplicationMessage, value); }
}
private RelayCommand<string> _SetViewCommand = null;
public RelayCommand<string> SetViewCommand
{
get
{
if (_SetViewCommand == null)
{
_SetViewCommand = new RelayCommand<string>((v) =>
{
ActiveView = v;
}
}
return _SetViewCommand;
}
}
}
As you see, your commands will simply manipulate the value of the public property to the appropriate value. Now your View can build on top of this property and use DataTriggers to load proper Content:
<Button Content="Normal" Command="{Binding SetViewCommand}" CommandParameter="Normal" />
<Button Content="Edit" Command="{Binding SetViewCommand}" CommandParameter="Edit" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ActiveView}" Value="Normal">
<Setter Property="Content">
<Setter.Value>
<UserControl1 />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ActiveView}" Value="Edit">
<Setter Property="Content">
<Setter.Value>
<UserControl2 />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Edit
Answering your comment:
view doesn't really interact with the viewModel: That's incorrect. View always interacts with the VM, it is the VM that doesn't know anything about the view. About your confusion, understand that you don't need to create one VM per user control. A VM is created against a View, not a control. So you'll probably have a single ViewModel named MainVM, exposing all the public properties that your view will bind to. Each User Control can then use only the properties that it is interested in. A trigger can then switch the view from one User Control to another when a particular property of the underlying VM is changed. The new User Control will then show correct data since it will be bound to the VM properties that it wants to work with.
Just remember that the VM is not interested in (nor have a way of) knowing how view is using it. It just needs to maintain its state correctly. View can then respond to the state changes and update itself accordingly.

Is there a way of using virtualization with hidden panels or expanders?

I'm trying to improve performance with my WPF application and I'm having problems with a complex ItemsControl. Although I've added Virtualization, there's still a performance problem and I think I've worked out why.
Each item contains a series of expandable areas. So the user sees a summary at the start but can drill down by expanding to see more information. Here's how it looks:
As you can see, there's some nested ItemsControls. So each of the top level items has a bunch of Hidden controls. The virtualization prevents off-screen items from loading, but not the hidden items within the items themselves. As a result, the relatively simple initial layout takes a significant time. Flicking around some of these views, 87% of time is spent parsing and Layout, and it takes a few seconds to load.
I'd much rather have it take 200ms to expand when (if!) the user decides to, rather than 2s to load the page as a whole.
Asking for advice really. I can't think of a nice way of adding the controls using MVVM however. Is there any expander, or visibility based virtualization supported in WPF or would I be creating my own implementation?
The 87% figure comes from the diagnostics:
If you simply have
- Expander
Container
some bindings
- Expander
Container
some bindings
+ Expander
+ Expander
... invisible items
Then yes, Container and all bindings are initialized at the moment when view is displayed (and ItemsControl creates ContentPresenter for visible items).
If you want to virtualize content of Expander when it's collapsed, then you can use data-templating
public ObservableCollection<Item> Items = ... // bind ItemsControl.ItemsSource to this
class Item : INotifyPropertyChanged
{
bool _isExpanded;
public bool IsExpanded // bind Expander.IsExpanded to this
{
get { return _isExpanded; }
set
{
Data = value ? new SubItem(this) : null;
OnPropertyChanged(nameof(Data));
}
}
public object Data {get; private set;} // bind item Content to this
}
public SubItem: INotifyPropertyChanged { ... }
I hope there is no need to explain how to to do data-templating of SubItem in xaml.
If you do that then initially Data == null and nothing except Expander is loaded. As soon as it's expanded (by user or programmatically) view will create visuals.
I thought I'd put the details of the solution, which is pretty much a direct implementation of Sinatr's answer.
I used a content control, with a very simple data template selector. The template selector simply checks if the content item is null, and chooses between two data templates:
public class VirtualizationNullTemplateSelector : DataTemplateSelector
{
public DataTemplate NullTemplate { get; set; }
public DataTemplate Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
{
return NullTemplate;
}
else
{
return Template;
}
}
}
The reason for this is that the ContentControl I used still lays out the data template even if the content is null. So I set these two templates in the xaml:
<ContentControl Content="{Binding VirtualizedViewModel}" Grid.Row="1" Grid.ColumnSpan="2" ><!--Visibility="{Binding Expanded}"-->
<ContentControl.Resources>
<DataTemplate x:Key="Template">
<StackPanel>
...complex layout that isn't often seen...
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="NullTemplate"/>
</ContentControl.Resources>
<ContentControl.ContentTemplateSelector>
<Helpers:VirtualizationNullTemplateSelector Template="{StaticResource Template}" NullTemplate="{StaticResource NullTemplate}"/>
</ContentControl.ContentTemplateSelector>
</ContentControl>
Finally, rather than using a whole new class for a sub-item, it's pretty simple to create a "VirtualizedViewModel" object in your view model that references "this":
private bool expanded;
public bool Expanded
{
get { return expanded; }
set
{
if (expanded != value)
{
expanded = value;
NotifyOfPropertyChange(() => VirtualizedViewModel);
NotifyOfPropertyChange(() => Expanded);
}
}
}
public MyViewModel VirtualizedViewModel
{
get
{
if (Expanded)
{
return this;
}
else
{
return null;
}
}
}
I've reduced the 2-3s loading time by about by about 75% and it seems much more reasonable now.
This simple solution helped me:
<Expander x:Name="exp1">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp1, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
<Expander x:Name="exp2">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp2, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
An easier way to achieve this is to change the default Visibility of the contents to Collapsed. In this case WPF won't create it initially, but only when a Trigger sets it to Visible:
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility"Value="Visible" TargetName="ExpandSite"/>
</Trigger>
Here "ExpandSite" is the ContentPresenter within the default ControlTemplate of the Expander control.
Note that this has been fixed in .NET - see the default style from the WPF sources on github.
In case you have an older version, you can still use this fixed control template to update the old one with an implicit style.
You can apply the same technique to any other panel or control.
It's easy to check if the control was already created with Snoop. Once you attached it to your application, you can filter the visual tree with the textbox on the top left. If you don't find one control in the tree, it means it was not created yet.

Correctly handling document-close and tool-hide in a WPF app with AvalonDock+Caliburn Micro

I'm trying to use both AvalonDock 2.0 (MVVM-compliant) and Caliburn Micro in my WPF application. All works fine, except for a couple of issues connected with closing document panes or hiding tool panes.
My main viewmodel derives from Conductor<IScreen>.Collection.OneActive and exposes two BindableCollection's of Screen-derived viewmodels for Tools and Documents; the corresponding relevant XAML is like:
<xcad:DockingManager Grid.Row="1"
AnchorablesSource="{Binding Path=Tools}"
DocumentsSource="{Binding Path=Documents}"
ActiveContent="{Binding Path=ActiveItem, Mode=TwoWay}">
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.DisplayName}" />
</Style>
</xcad:DockingManager.LayoutItemContainerStyle>
<xcad:DockingManager.LayoutItemTemplateSelector>
<views:AutobinderTemplateSelector>
<views:AutobinderTemplateSelector.Template>
<DataTemplate>
<ContentControl cal:View.Model="{Binding . }" IsTabStop="False" />
</DataTemplate>
</views:AutobinderTemplateSelector.Template>
</views:AutobinderTemplateSelector>
</xcad:DockingManager.LayoutItemTemplateSelector>
<xcad:LayoutRoot>
<xcad:LayoutPanel Orientation="Horizontal">
<xcad:LayoutAnchorablePane DockHeight="150" DockMinWidth="200">
</xcad:LayoutAnchorablePane>
<xcad:LayoutDocumentPane/>
</xcad:LayoutPanel>
</xcad:LayoutRoot>
</xcad:DockingManager>
The template selector is as simple as that:
public class AutobinderTemplateSelector : DataTemplateSelector
{
public DataTemplate Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return Template;
}
}
1. Closing Documents
The first issue comes when handling document-pane close. AD has its document handling mechanism, which should be synchronized with CM's one. CM is based on a screen conductor; when a screen needs to be closed, the method TryClose is used to close it if possible (i.e. unless a guard method tells the framework that the screen cannot be closed, e.g. because the document is dirty). To let AD play with CM I'm using a workaround similar to that described in Prevent document from closing in DockingManager, where the main view code directly calls this method handling the docking manager closing event: when AD is closing the document, call the underlying VM guard method and cancel if required; if not cancelled, then AD goes on closing thus firing the DocumentClosed event.
To see if this could work, I first created a public TryClose method in the document viewmodel base, essentially duplicating the code in the CM TryClose override, like (IsDirty is a protected virtual method overridden by descendant viewmodels):
public bool CanClose()
{
if (!IsDirty()) return true;
MessageBoxAction prompt = new MessageBoxAction
{ ...prompt message here... };
bool bResult = true;
prompt.Completed += (sender, args) =>
{
bResult = prompt.Result == MessageBoxResult.Yes;
};
prompt.Execute(null);
return bResult;
}
This is the method called by the main view code behind in the handlers for AD document closing and document closed:
private void OnDocumentClosing(object sender, DocumentClosingEventArgs e)
{
DocumentBase doc = e.Document.Content as DocumentBase;
if (doc == null) return;
e.Cancel = !doc.CanClose();
}
private void OnDocumentClosed(object sender, DocumentClosedEventArgs e)
{
DocumentBase editor = e.Document.Content as DocumentBase;
if (doc != null) doc.TryClose();
}
Note that I cannot directly call TryClose in OnDocumentClosing, as this would cause null object reference errors in AD. This is really ugly but it works. I can now close documents and my guard methods are called appropriately before proceeding. Anyway, it would be nice to get suggestions for a less hacky solution here.
2. Hiding Tools
Another issue arises from hiding the tools panes. In this case, AD should just hide them. The AD control visibility can be bound to an IsVisible boolean property in my viewmodels implementing tool panes, using a BooleanToVisibility converter. To this end I just add the binding in the XAML:
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
...
<Setter Property="Visibility"
Value="{Binding Model.IsVisible, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityCvt}}"/>
...
Now, if I hide a tool pane by clicking on its X button, I can see that my VM IsVisible property is set to false as expected, and the pane is hidden. Then, if I programmatically set this property back to true the pane is not shown. Even restoring the layout does not work: I can see that when the application starts and the object corresponding to the hidden VM is being added to the Tools collection its IsVisible is already false. To have it back, I must set this to true and then restore the layout. If I miss any of the two steps, the pane remains hidden. Clearly I'm not following the intended implementation strategy here. Could anyone point me in the right direction?

Can't click textbox after validation fails

I'm working on trying to implement validation in WPF, and running into an issue where I can't click on or change the value of a textbox after validation fails.
I have a User class (which implements IDataErrorInfo), which contains the following relevant code:
public virtual string Error
{
get
{
return null;
}
}
public virtual string this[string name]
{
get
{
string result = null;
if (name == "uFirstName")
{
if (String.IsNullOrEmpty(this.uFirstName))
{
return "Must enter a first name!";
}
}
return result;
}
}
Then, over in my MainWindow code-behind, I have this code to hookup my combobox:
comboBox1.ItemsSource = Users; //Users is a collection of Users
Finally, in my MainWindow xaml, I have this:
<ComboBox Name="comboBox1" ItemTemplate="{StaticResource userTemplate}" />
<TextBox Name="textBox1" DataContext="{Binding ElementName=comboBox1,
Path=SelectedItem}" Text="{Binding Path=uFirstName, ValidatesOnDataErrors=True,
NotifyOnValidationError=True}"/>
And indeed, the validation does fire when I delete the text and the textbox gets a nice red border. However, the changes are still sent back to Users (uFirstName gets set to nothing!). Even worse though, I now cannot edit the value in that textbox unless I tab back into it.
What is needed to make sure the value isn't sent back if it isn't valid, and allow the textbox to be edited if is invalid?
The IDataErrorInfo implementation is only there to report errors. You should implement your verification in the uFirstName property. If you don't think a certain verification on a property is true for all instances, you should start looking into ValidationRule. This is a better way of implementing Validation in WPF. ValidationRule will not set value to the source, if the validation fails.
The textbox might be acting strange because of a system level style? Not exactly sure on why you are having trouble there.

Categories