Signum Framework Logo
"Open framework that encourages convention over configuration, using C# code,
not XML files, to model at the right level of abstraction and achieve deadlines.
...but also has a full Linq provider, and syncs the schema for you!"
Login
RSS

Search

»



Main
Index
Map
Videos
Download
Source code
Tutorials
Forum
FAQ



Image



PoweredBy

Introduction

Entity Controls are a family of controls conveniently designed to write in Xaml when creating the presentation of your entities.

There are three main techniques that allow you to simplify the code:

  • Using a more focused (and limited) Binding strategy using the Common class.
  • A centralized repository of settings for each entity type, saving us to configure each entity control independently.
  • Integrating the line label at the right in the same control (ValueLine, EntityLine, FileLine & EntityCombo).

Let's build the control to present the BugDN type using Entity Controls.

The first things are to create a new WPF UserControl, import the necessary Xml namespaces, and set the UserControl TypeContext appropriately.

<UserControl x:Class="Bugs.Windows.Controls.Bug"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:m="clr-namespace:Signum.Windows;assembly=Signum.Windows"
    xmlns:d="clr-namespace:Bugs.Entities;assembly=Bugs.Entities"
    m:Common.TypeContext="d:BugDN" MinWidth="400">

</UserControl>

ValueLine

ValueLine is the only control you need to display simple values like numbers (int, long, float, double, decimal ...), strings, bools, DateTimes.

   <m:ValueLine m:Common.Route="Start" />

Value line is a very heterogeneous control. It's the only one not inheriting from EntityBase, and contains the following Dependency Properties:

public partial class ValueLine : UserControl
{
   public string LabelText;
   public object Value;
   public Type ValueType; 
   public ValueLineType ValueLineType;
   public Control ValueControl; 
   (...)
}

As other controls, ValueLine contains a label at the right side controlled by LabelText property that can be shown/hidden and resized using Common LabelVisible and LabelWidth properties.

On the left side, ValueLine places whatever control necessary to display and edit the corresponding value based on the Property Type, this reduces the number of controls you have to remember.

To archive that, ValueLine uses an algorithm like this:

Image

Every item here is a property, and the arrows indicate the direction the information flows.

  • Step 1: Common.Route, as usual, initializes LabelText, ValueType and binds ValueBinding to your entity property.
  • Step 2: Based on ValueType value, an using the following table, ValueLineType is calculated:

ValueTypeValueLineType
boolBoolean
double, decimal, singleDecimalNumber
byte, sbyte, short, int, long, uint, ushort, ulongNumber
Currency
DateTimeDateTime
char, stringString
any EnumEnum
ColorDNColor

  • Step 3: Using ValueLineType, the right control is created and initialized. The Binding to Value property is disassembled and a similar one is assembled to the right property of the new control, following this table:

ValueLineTypeControlBinding PropertyReadOnlyProperty
BooleanCheckBoxIsChecked!IsEnabled
DecimalNumberm:NumericTextBox (decimal number format)Value IsReadOnly
Number m:NumericTextBox (number format)ValueIsReadOnly
Currencym:NumericTextBox (currency format)ValueIsReadOnly
DateTimeDateTimePickerValueIsReadOnly
StringTextBoxTextIsReadOnly
Enum ComboBoxSelectedItemIsReadOnly
Color m:ColorPickerSelectedColorIsReadOnly

Using this strategy we save a lot of code by inferring information, while exposing the resorts to change the behaviour if needed: At any given moment you can set the value of some property explicitly, and it will be preserved, for example, you could write:

  <m:ValueLine m:Common.Route="Price"/> <!-- Simple Version ValueLineType="DecimalNumber" and LabelText="Price" -->
  <m:ValueLine m:Common.Route="Price" ValueLineType="Currency"/> <!-- Override ValueLineType, LabelText="Price" -->
  <m:ValueLine m:Common.Route="Price" ValueLineType="Currency" LabelText="_Price" /> <!-- Override LabelText using label syntax for keyboard shortcut! -->

Note in the first table no type goes to Currency ValueLineType. If you want to use it you will need to assign it explicitely like in the example

Example

Writing a few stacked ValueLines is as simple as writing them inside a StackPanel.

<UserControl x:Class="Bugs.Windows.Controls.Bug"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:m="clr-namespace:Signum.Windows;assembly=Signum.Windows"
    xmlns:d="clr-namespace:Bugs.Entities;assembly=Bugs.Entities"
    m:Common.TypeContext="d:BugDN" MinWidth="400">
    <StackPanel>
        <m:ValueLine m:Common.Route="Start" />
        <m:ValueLine m:Common.Route="End" />
        <m:ValueLine m:Common.Route="Hours" />
        <m:ValueLine m:Common.Route="Status" />
        <GroupBox Header="Description">
            <TextBox AcceptsReturn="True" AcceptsTab="True" Text="{Binding Description, NotifyOnValidationError = true, 
ValidatesOnExceptions = true, ValidatesOnDataErrors = true}" Height="100" />
        </GroupBox>
    </StackPanel>
</UserControl>

Also, since Description is usually quite long, it's better to use a big textbox that accepts returns and tabs.

There's no special support for 'big textboxes' in Signum Windows, but fortunately Signum.Windows uses just plain Bindings and DataContexts internally, so nothing stops you from adding your own personal control and binding it manually. Signum Windows is a good WPF citizen.

Look at the results in a NormalWindow:

Image

This interface is fully functional, no code behind required. Also, it's already prepared for an high interactive validation experience based on the Validation you put in your entities, let's suppose we decorate some BugDN's properties like this:

  [StringLengthValidator(Min = 10)]
  public string Description
  
  (...)
  
  [NumberIsValidator(ComparisonType.GreaterThanOrEqual, 0)]
  public decimal? Hours


Then, any time the entity becomes wrong, red boxes appear on the wrong data and also a ErrorSummary reflects a list of all the errors at the bottom-left of your NormalWindow. All this for free! Cool isn't it?

Image

EntityBase abstract class

EntityBase is the base control for EntityLine, EntityCombo and EntityList, it defines all the shared properties and some shared internal behaviour.

An EntityBase is a control that holds an entity, being able to remove or view the entity if any, or create or find one in the database if not.

These four actions are represented by four buttons and will become a repeated abstraction in Signum.Windows:

Image

Let's see what public member we are going to find in any EntityBase.

public class EntityBase: UserControl
{
   public string LabelText {...}  
   public object Entity {...}
   public Type EntityType {...}
   public Type[] Implementations {...}
   public DataTemplate EntityTemplate {...}

public bool Create {...} public bool Find {...} public bool View {...} public bool Remove {...}

public bool ViewOnCreate {...}

public event Func<object> Creating; public event Func<object> Finding; public event Func<object, object> Viewing; public event Func<object, bool> Removing; }

  • LabelText: The text on the left label, accepts label underscore syntax for keyboard shortcuts. Last token of Common.Route is used as default value. Ignored on EntityList.
  • Entity: This property is the one that holds the entity. This entity could be derived from IdentifiableEntity or just be a Lazy object. The entity could also be null.
  • EntityType: The static type of the property EntityBase is binded too. It's necessary to be the static type, otherwise, when null, EntityBase will have no clue how to create it.
  • Implementations: When set, indicates that EntityType is just the type of the variable that holds it, but the actual entities can be any of the Types on Implementations array. This has implications on Create and Find (showing a TypeSelectorWindow first), View visibility, EntityLine AutoCompletion and EntityCombo elements.
  • EntityTemplate: The DataTemplate that will be used in any derived control (EntityLine, EntityCombo, EntityList)

  • Create: Enables/Disables the ability to Create when Entity is null. Default is true, but EntitySetting can turn it off.
  • Find: Enables/Disables the ability to Find when Entity is null. Default is true, but not having a Query with queryName == EntityType will turn it off.
  • View: Enables/Disables the ability to View when Entity is not null. Default is true, but EntitySetting can turn it off.
  • Remove: Enables/Disables the ability to Remove when Entity is not null. Default is true.

  • ViewOnCreate: Enables/Disables that, after creating the entity, the entity is viewed.

Also, four events allow us to override how to deal with the entity entity for this control only (for all the entities of the same type, look EntitySettings):

  • Creating: Overrides how the entity is created. Returning null means no action is taken. The default behaviour is to use the Constructor.
  • Viewing: Overrides how the entity has to be viewed. Returning something different than null changes the entity as well. The default behaviour is to use Navigator.View.
  • Find: Overrides how the entity is found. Returning null means no action is taken. The default behaviour is to use Navigator.Find with the type of the entity as queryName.
  • Removing: Overrides how the entity is found. Returning true to allow the entity to be removed. The default behaviour is to allow removing.

Note that Removing an entity means removing the entity from the property (relationship) not from the database

Finally, note that in case you want to disable any kind of modifications on a given property the right thing to do is not to disable all the buttons (even view could modify the entity when clone is needed). Instead use Common.IsReadOnly.

EntityLine

EntityLine is a line control that contains, in his right side, a placeholder for an entity. This placeholder is just a box with the four buttons (Create, Find, View and Remove) conveniently hidden depending if the entity is null or not.

Image

EntityLine is used for mapping associations IdentifiableEntities where the number of entities of the type is expected to be big, so a ComboBox is not a feasible solution.

EntityLine, however, has a very powerfull feature to quickly find entities in the database without opening a Search window: By double-clicking on the placeholder (single click if the place holder is void) or pressing F2 when focused, the place holder transforms into a AutoCompleteTextBox that retrieves the top N Lazy objects of the given type that look similar to the string written by the user. This feature also works when Implementations is set (Common.Route does it for you if necessary) so the entities that appear in the AutoCompletion could be of any of the implementation types.

Image

EntityLine extends EntityBase adding some members to control this feature:

public partial class EntityLine : EntityBase
{ 
    public bool AutoComplete
    public event Func<string, IEnumerable<Lazy>> AutoCompleting;
    (...)
}

Using AutoComplete property you can disable auto-completion for a single EntityLine.

By subscribing the event AutoCompleting you can control the Lazy objects that are retrieved.

EntityCombo

EntityCombo is the small sibling of EntityLine, it's used in the same situations, but when the expected range of possible entities to choose from is smaller and you want to show them all.

Image

In order show the entities of the combo, it has to be loaded. EntityCombo adds two members to control how and when this loading has to be done.

public partial class EntityCombo : EntityBase
{ 
    public LoadDataTrigger LoadDataTrigger;
    public event Func<IEnumerable> LoadData;
    public bool ForceLoadLazy;
}

public enum LoadDataTrigger { OnLoad, OnExpand, }

  • LoadDataTrigger property: Controls when the entity is loaded, the default is OnExpand to improve the time a NormalWindow takes to load.
  • LoadData event: By subscribing this method you can take control of where the data comes from.
  • ForceLoadLazy property: Controls that, even if EntityType is not Lazy, the entities in the combo should be lazy to improve performance.

EntityCombo also works when Implementations is set (Common.Route does it for you if necessary) so the entities that appear list could be of any of the implementation types.

Also, EntityCombo overrides the default value of Find and Remove to false because these operations are not usual on this control. Of course, they still obey EntitySettings if nothing is set explicitly.

Example

Let's improve our BugDN control with what we have learned, EntityLine and EntityCombos:

<UserControl x:Class="Bugs.Windows.Controls.Bug"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:m="clr-namespace:Signum.Windows;assembly=Signum.Windows"
    xmlns:d="clr-namespace:Bugs.Entities;assembly=Bugs.Entities"
    m:Common.TypeContext="{x:Type d:BugDN}" MinWidth="400">
    <StackPanel>
        <Grid m:Common.LabelWidth="50">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <m:ValueLine m:Common.Route="Start" LabelText="_Start"/>
            <m:ValueLine m:Common.Route="End" LabelText="_End" Grid.Column="1"/>
            <m:ValueLine m:Common.Route="Hours" LabelText="_Hours" Grid.Row="1" Grid.Column="1"/>
            <m:ValueLine m:Common.Route="Status" LabelText="St_atus" Grid.Row="1"/>
        </Grid>
        <m:EntityLine m:Common.Route="Discoverer" LabelText="_Discoverer"/>
        <m:EntityCombo m:Common.Route="Fixer" LabelText="_Fixer" ForceLoadLazy="True"/>
        <m:EntityLine m:Common.Route="Project" LabelText="_Project"/>
        <GroupBox Header="Description">
            <TextBox AcceptsReturn="True" AcceptsTab="True" Text="{Binding Description, NotifyOnValidationError = true, ValidatesOnExceptions = true, ValidatesOnDataErrors = true}" Height="100" />
        </GroupBox>
    </StackPanel>
</UserControl>

Notice that we have arranged our previous four ValueLines in a 2x2 grid using LabelWidth="50" to reduce it.

Also, we have added LabelText with keyboard shortcuts all around for our user's pleasure.

Finally we have added two EntityLine and one EntityCombo. The screen now looks like this:

Image

EntityList

EntityList is the control provided by Signum Windows to represent lists on the user interface. Internally it uses a WPF ListBox, so it will take advantage of a collection implementing INotifyCollectionChanged, like MList.

Image

EntityList inherits from EntityBase, and the underlying type of the collection is the one used for retrieving EntitySettings for example.

Also, Implementations work as espected on any EntityBase, showing a TypeSelectorWindow when Create or Find is clicked.

EntityList needs to add some properties:

public partial class EntityList : EntityBase
{
    public IList Entities {...};
    public Type EntitiesType {...};
    public SelectionMode SelectionMode {...};
    public bool Move {...};
}
    
public enum SelectionMode
{
    Single = 0,
    Multiple = 1,
    Extended = 2,
}

  • Entities: This property is meant to be bound to your collection. The Entity property is now bound to the current element selected.
  • EntitiesType: Determines the 'static' type of the collection on Entities.
  • SelectionMode: Using SelectionMode enumeration
  • Move: Enables the ability of Moving Up and Down the elements on the bound collection. A pair of new buttons with an arrows will appear.

As usual, Entites and EntitiesType are automatically set by Common.Route.

Also, since Common.Route allows Source Transversal property path expressions ('/'), it's really easy to make a Master-Detail view of your entities. Let's see an example:

DataBorder

Back in the old days of WinForms (ok, the present for most of the people :S) user interface flickering was a problem you tried to avoid. Thanks to WPF, now there's just no flickering at all.

In Master-Detail scenarios, however, some visual indicator is useful on the Detail view when the selected entity changes, something like 'forcing flickering'. Kind of retro, but users are more happy this way. Also, it's useful to make the whole Detail view to disappear when no entity is selected.

DataBorder inherits from Border, and does just these two things:

  • When DataContext is null, sets Child.Visibility to Hidden, otherwise Visible.
  • When DataContext changes, starts an animation that moves Child Opacity from 0 to 1 in 0.1ms.

There's currently no public properties to customize this behavior, and DataBorder is used just as any other Border.

DataBorder is a very odd EntityControl: It works with DataContext but nothing to do with Signum.Entities.

Example (EntityList & DataBorder)

To add support to edit, create and remove the CommentDN entities in our Bug control we just need to add the following:

   <m:EntityList m:Common.Route="Comments" Height="100"/>
   <StackPanel m:Common.Route="Comments/">
      <m:EntityCombo m:Common.Route="Writer"/>
      <m:ValueLine m:Common.Route="Date" />
      <TextBox AcceptsReturn="True" AcceptsTab="True" Text="{Binding Text, NotifyOnValidationError = true, 
ValidatesOnExceptions = true, ValidatesOnDataErrors = true}" Height="100" />
   </StackPanel>

First we have created an EntityList, and assigned Comments to Common.Route property in order to create all the proper bindings. Since we haven't added any EntitySetting for CommendDN, nor any Query with typeof(CommendDN) as queryName, View, ViewOnCreate and Find has been automatically disabled.

Secondly, we have used a StackPanel to contain the details view of the current selected item. Just by adding '/' at the end of the expression we are telling Common.Route that we are interested in the current element, not the collection. Inside the panel we have added some EntityControls as we previously explained.

Now let's complete our BugDN control with the previous code:

<UserControl x:Class="Bugs.Windows.Controls.Bug"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:m="clr-namespace:Signum.Windows;assembly=Signum.Windows"
    xmlns:d="clr-namespace:Bugs.Entities;assembly=Bugs.Entities"
    m:Common.TypeContext="d:BugDN" MinWidth="400">
    <StackPanel>
        <Grid m:Common.LabelWidth="50">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <m:ValueLine m:Common.Route="Start" LabelText="_Start"/>
            <m:ValueLine m:Common.Route="End" LabelText="_End" Grid.Column="1"/>
            <m:ValueLine m:Common.Route="Hours" LabelText="_Hours" Grid.Row="1" Grid.Column="1"/>
            <m:ValueLine m:Common.Route="Status"  LabelText="St_atus" Grid.Row="1"/>
        </Grid>
        <m:EntityLine m:Common.Route="Discoverer" LabelText="_Discoverer"/>
        <m:EntityCombo m:Common.Route="Fixer" LabelText="_Fixer"/>
        <m:EntityLine m:Common.Route="Project" LabelText="_Project"/>
        <GroupBox Header="Description">
            <TextBox AcceptsReturn="True" AcceptsTab="True" Text="{Binding Description, NotifyOnValidationError = true,
 ValidatesOnExceptions = true, ValidatesOnDataErrors = true}" Height="100" />
        </GroupBox>
        <GroupBox Header="Comments">
            <StackPanel>
                <m:EntityList m:Common.Route="Comments" ViewOnCreate="False" Height="100"/>
                <m:DataBorder m:Common.Route="Comments/">
                    <StackPanel>
                        <m:ValueLine m:Common.Route="Date" />
                        <m:EntityCombo m:Common.Route="Writer"/>
                        <TextBox AcceptsReturn="True" AcceptsTab="True" Text="{Binding Text, 
NotifyOnValidationError = true, ValidatesOnExceptions = true, ValidatesOnDataErrors = true}" Height="100" />
                    </StackPanel>
                </m:DataBorder>
            </StackPanel>
        </GroupBox>
    </StackPanel>
</UserControl>

Notice we have done some small changes:
  • We have added a DataBorder to make the details panel blink when the selected comment changes. (It's not possible to appreciate the effect on the screenshot)
  • We have added a WPF GroupBox for Comments. This is a good example of how easy is to integrate EntityControls with other controls.

This is the end result:

Image

Notice how the open EntityCombo shows Customers and Developers indistinctly. This is because Writer property is a IBugDiscoverer that, ultimately, is implemented by CustomerDN and DeveloperDN. All this without writing any single line of code in your code-behind.

FileLine

FileLine is a line control (label on left side) similar to EntityLine, but adapted for dealing with files.

Image

As ValueLine, it doesn't inherit from EntityBase, getting its property and behaviour, but it shares a common philosophy and look and feel.

Here is the code:

public partial class FileLine: UserControl
{
   public string LabelText {...}
   public object Entity {...}
   public Type EntityType {...}
   public DataTemplate EntityTemplate {...}
   
   public bool Open {...}
   public bool View {...}
   public bool Save {...}
   public bool Remove {...}

public event Func<object> Opening {...} public event Action<object> Saving {...} public event Action<object> Viewing {...} public event Func<object, bool> Removing {...}

public event Action<FileDialog> CustomizeFileDialog; (...) }

LabelText, Entity, EntityType and EntityTempleate properties work the same way EntityBase does.

Notice there's no support for Implementations on FileLine

As you see, Create and Find have disappeared, instead we have:
  • Open: Opens a OpenFileDialog, reads the file and writes into the entity.
  • Save: Opens a SaveFileDialog, reads the entity and writes the file.

Also, View has been redefined:
  • View: Reads the entity, writes it in a temp file, starts the application associated with the extension.

CustomizeFileDialog event, finally, is an event that allows you to modify the OpenFileDialog and SaveFileDialog just before they are open (i.e. Change Filters, DefaultExt... )

IFileDN

The default implementation for Opening, Saving and Viewing needs the entity bound to Entity property to be a file entity, that is, implement the following interface defined in Signum.Entities:

public interface IFileDN
{
   byte[] BinaryFile { get; set; } //The actual data of the file
   string FileName { get; set; } //The name of the file with extension (not the full path)
}

Signum.Entities also has defines FileDN, an EmbeddedEntity that implements IFileDN.

Be carefull, since it's an embedded entity, the file data won't be able to be shared by many entities, and the data will come with the entity at all times, potentially producing a performance problem. Fortunately, nothing stops you from creating a new implementation of IFileDN that inherits from IdentifiableEntity for example. FileLine also supports the bound property to be a lazy.

Finally, if you want to do something different than dealing with BinaryFile and FileName properties (i.e. Store LastWriteTime, MD5 hash...) then implementing the events to override the default behaviour when Opening, Saving, Viewing and Removing is a better idea. This way there's no point in implementing IFileDN.

Example

Let's add the feature of adding screenshots on comments. First we need to create a ScreenshotDN entity. Since we don't want to move these potentially big images very often, we are going to define it as a IdentifiableEntity:

    [Serializable]
    public class ScreenShotDN : IdentifiableEntity, IFileDN
    {
        [NotNullable]
        byte[] binaryFile;
        [NotNullValidator]
        public byte[] BinaryFile
        {
            get { return binaryFile; }
            set { Set(ref binaryFile, value, "BinaryFile"); }
        }

[NotNullable] string fileName; [StringLengthValidator(Min = 3)] public string FileName { get { return fileName; } set { Set(ref fileName, value, "FileName"); } }

public override string ToString() { return "{0} [{1}]".Formato(fileName, binaryFile.TryCC(a => StringExtensions.ToComputerSize(a.Length))); } }


ToComputerSize is a very useful method in StringExtensions for transforming number of bytes in human-readable computer sizes as 2.4 MBytes

And create a Lazy relationship between CommentDN and ScreenShotDN:

   [Serializable]
   public class CommentDN : EmbeddedEntity
   {
        (...)

Lazy<ScreenShotDN> screenShot; public Lazy<ScreenShotDN> ScreenShot { get { return screenShot; } set { Set(ref screenShot, value, "ScreenShot"); } }

(...) }

Let's Sync the DB Schema to create the necessary tables, and get into our User Interface. We just need to add a line like this below Writer EntityCombo:

    <m:FileLine m:Common.Route="ScreenShot" LabelText="_ScreenShot" CustomizeFileDialog="FileLine_CustomizeFileDialog"/>

And let's implement CustomezeFileDialog event in our clean and empty code-behind file:

  private void FileLine_CustomizeFileDialog(FileDialog obj)
  {
      obj.Filter = "Bitmap Image|*.bmp|JPEG Image|*.jpg;*.jpeg|PNG Image|*.png|GIF Image|*.gif";
  }

ErrorSummary

And last but not least, ErrorSummary.

ErrorSummany is different to the rest of controls, it's not useful to view or edit your entities' properties, but for explicitly showing the list of all errors on a WPF subtree.

It unifies two different kinds of errors to simplify user experience:

  • Binding Errors: Error intercepted from Validation.Error event bubbling up from the bindings on each control inside the WPF sub-tree. Usually format errors that never reach the entity.
  • Entity Validations: Errors provided by the IDataErrorInfo.Error property of the entity in the current DataContext. These are Validation errors that the entity is aware of.

ErrorSummary just has to be placed in your Xaml to work, no configuration needed. When placed like this it uses his parent as the ValidationTarget control, intercepting Validation.Error events on it and using his DataContext. You can, however, set the ValidationTarget property manually.

Validation.Error events usually bubble up until they find an ErrorSummary to stop them. If you want your ErrorSummary to allow events to continue bubbling up set LetErrorsBubble to true.

Finally, NormalWindow and AdminWindow place an ErrorSummary in the botton-left of the window targeting your custom control.

Example

Since ErrorSummary does not explore sub entities, the one in NormalWindow corner does not show validation errors on each CommentDN. Placing an ErrorSummary on the detail view of a comment could be useful:

<UserControl x:Class="Bugs.Windows.Controls.Bug"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:m="clr-namespace:Signum.Windows;assembly=Signum.Windows"
    xmlns:d="clr-namespace:Bugs.Entities;assembly=Bugs.Entities"
    m:Common.TypeContext="d:BugDN" MinWidth="400">
    <StackPanel>
        <Grid m:Common.LabelWidth="50">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <m:ValueLine m:Common.Route="Start" LabelText="_Start"/>
            <m:ValueLine m:Common.Route="End" LabelText="_End" Grid.Column="1"/>
            <m:ValueLine m:Common.Route="Hours" LabelText="_Hours" Grid.Row="1" Grid.Column="1"/>
            <m:ValueLine m:Common.Route="Status"  LabelText="St_atus" Grid.Row="1"/>
        </Grid>
        <m:EntityLine m:Common.Route="Discoverer" LabelText="_Discoverer"/>
        <m:EntityCombo m:Common.Route="Fixer" LabelText="_Fixer"/>
        <m:EntityLine m:Common.Route="Project" LabelText="_Project"/>
        <GroupBox Header="Description">
            <TextBox AcceptsReturn="True" AcceptsTab="True" Text="{Binding Description, NotifyOnValidationError = true, ValidatesOnExceptions = true, ValidatesOnDataErrors = true}" Height="100" />
        </GroupBox>
        <GroupBox Header="Comments">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" MinWidth="200"/>
                    <ColumnDefinition Width="*" MinWidth="200"/>
                </Grid.ColumnDefinitions>
                <m:EntityList m:Common.Route="Comments" ViewOnCreate="False" Grid.Column="0"/>
                <m:DataBorder m:Common.Route="Comments/" Grid.Column="1">
                    <StackPanel>
                        <m:ValueLine m:Common.Route="Date" />
                        <m:EntityCombo m:Common.Route="Writer"/>
                        <m:FileLine m:Common.Route="ScreenShot" LabelText="_ScreenShot" CustomizeFileDialog="FileLine_CustomizeFileDialog"/>
                        <TextBox AcceptsReturn="True" AcceptsTab="True" Text="{Binding Text, NotifyOnValidationError = true, ValidatesOnExceptions = true, ValidatesOnDataErrors = true}" Height="100" />
                        <m:ErrorSummary/> <!-- New -->
                    </StackPanel>
                </m:DataBorder>
            </Grid>
        </GroupBox>
    </StackPanel>
</UserControl>

Notice that we have also re-arranged Comments group in two columns. This is the final result of our Bug control in a NormalWindow:

Image

Creative Commons License Signum Framework Site by Signum Software is licensed under a Creative Commons Attribution 3.0 License.
Powered by ScrewTurn Wiki version 3.0.5.600.